diff --git a/docs/Makefile b/docs/Makefile
index 0313a269c0b7..82535df5b26b 100644
--- a/docs/Makefile
+++ b/docs/Makefile
@@ -43,7 +43,9 @@ update-openapi:
@WEBLATE_HIDE_VERSION=1 uv run --no-sync ../manage.py spectacular --skip-checks --fail-on-warn --validate > specs/openapi.yaml
update-docs:
- @uv run --no-sync ../manage.py list_addons -o snippets/addons-autogenerated.rst
+ @uv run --no-sync ../manage.py list_addons --sections events -o snippets/addon-events-autogenerated.rst
+ @uv run --no-sync ../manage.py list_addons --sections addons -o snippets/addons-autogenerated.rst
+ @uv run --no-sync ../manage.py list_addons --sections parameters -o snippets/addon-parameters-autogenerated.rst
@uv run --no-sync ../manage.py list_machinery -o snippets/machines-autogenerated.rst
@uv run --no-sync ../manage.py list_file_format_params > snippets/file-format-parameters.rst
@uv run --no-sync ../manage.py list_permissions --sections perms > snippets/permissions.rst
diff --git a/docs/admin/addons.rst b/docs/admin/addons.rst
index 8daaf17ce396..14669cbc9f05 100644
--- a/docs/admin/addons.rst
+++ b/docs/admin/addons.rst
@@ -17,8 +17,31 @@ translation project or component. Add-ons can be also installed site-wide in :re
.. image:: /screenshots/addons.webp
+.. include:: /snippets/addon-events-autogenerated.rst
+
.. include:: /snippets/addons-autogenerated.rst
+Obsolete add-ons
+++++++++++++++++
+
+.. _addon-weblate.xml.customize:
+
+Customize XML output
+--------------------
+
+.. versionadded:: 4.15
+
+.. versionremoved:: 5.13 Replaced by :ref:`file_format_params`.
+
+.. _addon-weblate.yaml.customize:
+
+Customize YAML output
+---------------------
+
+.. versionremoved:: 5.13 Replaced by :ref:`file_format_params`.
+
+.. include:: /snippets/addon-parameters-autogenerated.rst
+
Customizing list of add-ons
+++++++++++++++++++++++++++
diff --git a/docs/admin/management.rst b/docs/admin/management.rst
index b5cadd99ecdf..92b41a0cae60 100644
--- a/docs/admin/management.rst
+++ b/docs/admin/management.rst
@@ -717,6 +717,12 @@ list_addons
Lists add-ons in reStructuredText as a template for :doc:`/admin/addons`.
+.. weblate-admin-option:: --sections {events,addons,parameters}
+
+ Filter the generated output to just the add-on event sections, the built-in
+ add-on sections, or the shared add-on parameter sections. If omitted, all
+ generated sections are shown.
+
.. seealso::
:doc:`/contributing/documentation`
@@ -739,6 +745,18 @@ list_checks
Lists quality checks in reStructuredText as a template for :doc:`/admin/checks` and :doc:`/user/checks`.
+.. weblate-admin-option:: --sections {checks,flags}
+
+ Filter the generated output to just the quality check sections or the
+ shared check flag sections. If omitted, all generated sections are shown.
+
+.. note::
+
+ Using ``--output`` requires selecting exactly one value in ``--sections`` so
+ each generated snippet is written to its own file. This matches the
+ :doc:`/contributing/documentation` workflow and the ``make -C docs
+ update-docs`` targets.
+
.. seealso::
:doc:`/contributing/documentation`
diff --git a/docs/changes.rst b/docs/changes.rst
index 7b29904c2eeb..2e7d60f0598f 100644
--- a/docs/changes.rst
+++ b/docs/changes.rst
@@ -38,6 +38,7 @@ Weblate 5.17
* Screenshot and font upload forms now honor :setting:`ALLOWED_ASSET_SIZE` which now defaults to 10 MB.
* Expanded :doc:`/security/threat-model` to cover webhook trust boundaries and delegated authorization boundaries, and clarified the instance-wide 2FA enforcement path in :doc:`/admin/auth`.
* :ref:`manage-vcs-reset-reapply` now recreates missing translation files when possible and otherwise reports a clearer recovery error instead of failing later with a generic parse error.
+* Updated :doc:`/contributing/documentation` to describe the current ``make -C docs update-docs`` workflow for generated snippets.
.. rubric:: Bug fixes
@@ -58,6 +59,7 @@ Weblate 5.17
* Fixed false positive in :ref:`check-xml-chars-around-tags` for Arabic letter Waw ("و") adjacent to XML tags.
* :ref:`addon-weblate.git.squash` better handle commits applied upstream.
* :ref:`addon-weblate.cdn.cdnjs` validates parsed locations.
+* :wladmin:`list_checks` now requires exactly one ``--sections`` value when writing generated documentation to a file using ``--output``.
* Asset downloads now enforce :setting:`ALLOWED_ASSET_DOMAINS` across HTTP redirects for screenshot URL uploads and remote HTML fetching in :ref:`addon-weblate.cdn.cdnjs`.
* Improved security of :ref:`addon-weblate.webhook.webhook`.
* Watched translations on the dashboard now use a stable language-aware ordering.
diff --git a/docs/contributing/documentation.rst b/docs/contributing/documentation.rst
index 82be5adb9b69..46ba5e3426d5 100644
--- a/docs/contributing/documentation.rst
+++ b/docs/contributing/documentation.rst
@@ -26,8 +26,19 @@ Building the documentation locally
----------------------------------
Documentation can be also edited and built locally, the Python requirements are
-in the ``docs`` dependency group in :file:`pyproject.toml`. The build can be
-performed using :program:`ci/run-docs`.
+in the ``docs`` dependency group in :file:`pyproject.toml`. If you already use
+the full development environment, ``uv sync --all-extras --dev`` is enough. For
+documentation work only, ``uv sync --group docs`` is sufficient.
+
+The recommended local workflow is:
+
+.. code-block:: sh
+
+ make -C docs update-docs
+ ./ci/run-docs
+
+The :program:`ci/run-docs` wrapper builds the documentation with warnings
+treated as errors.
.. hint::
@@ -38,19 +49,30 @@ Translating the documentation
You can `translate the docs `_.
-Documenting permissions, checks, add-ons and automatic suggestions
-------------------------------------------------------------------
+Updating generated documentation snippets
+-----------------------------------------
Several documentation sections use templates generated from the code. The
-following management commands are available:
-
-* :wladmin:`list_addons`
-* :wladmin:`list_permissions`
-* :wladmin:`list_checks`
-* :wladmin:`list_machinery`
-* :wladmin:`list_file_format_params`
-* :wladmin:`list_change_events`
-
-All these commands output reStructuredText which is used as a template for the
-documentation. The easiest way to apply changes to the documentation is using
-visual diff in your editor.
+preferred way to refresh them is:
+
+.. code-block:: sh
+
+ make -C docs update-docs
+
+This target regenerates the snippets currently used by the documentation,
+including:
+
+* add-on events, built-in add-ons, and common add-on parameters
+* machine translation services
+* file format parameters and file format feature tables
+* permissions and built-in roles
+* checks and check flags
+
+Keep manually maintained text in the parent documentation page rather than
+adding it to autogenerated snippets. For example, :doc:`/admin/addons`
+includes three generated files for events, built-in add-ons, and common add-on
+parameters, while obsolete add-ons are maintained directly in the page.
+
+If you need to regenerate only one part, the individual management commands are
+documented in :doc:`/admin/management`, and the exact commands used by
+``update-docs`` are listed in :file:`docs/Makefile`.
diff --git a/docs/snippets/addon-events-autogenerated.rst b/docs/snippets/addon-events-autogenerated.rst
new file mode 100644
index 000000000000..3e0711471a2c
--- /dev/null
+++ b/docs/snippets/addon-events-autogenerated.rst
@@ -0,0 +1,110 @@
+
+.. AUTOGENERATED START: events
+.. This section is automatically generated by `./manage.py list_addons`. Do not edit manually.
+
+Events that trigger add-ons
++++++++++++++++++++++++++++
+.. _addon-event-add-on-installation:
+
+Add-on installation
+-------------------
+
+Triggered when add-on is being installed.
+
+.. _addon-event-component-update:
+
+Component update
+----------------
+
+Triggered whenever a change happens in a component such as:
+
+* Strings are changed in the repository.
+* A string is added.
+* A new translation is added.
+
+.. _addon-event-daily:
+
+Daily
+-----
+
+Triggered daily, but add-ons usually split the daily load between components depending on :setting:`BACKGROUND_TASKS`.
+
+.. _addon-event-event-change:
+
+Event change
+------------
+
+Triggered after a Change event is created.
+
+.. _addon-event-repository-post-add:
+
+Repository post-add
+-------------------
+
+Triggered just after the new translation is added and committed.
+
+.. _addon-event-repository-post-commit:
+
+Repository post-commit
+----------------------
+
+Triggered just after the changes are committed.
+
+.. _addon-event-repository-post-push:
+
+Repository post-push
+--------------------
+
+Triggered just after the repository is pushed upstream.
+
+.. _addon-event-repository-post-update:
+
+Repository post-update
+----------------------
+
+Triggered whenever new changes are pulled from the upstream repository.
+
+.. _addon-event-repository-pre-commit:
+
+Repository pre-commit
+---------------------
+
+Triggered just before the changes are committed.
+
+.. _addon-event-repository-pre-push:
+
+Repository pre-push
+-------------------
+
+Triggered just before the repository is pushed upstream.
+
+.. _addon-event-repository-pre-update:
+
+Repository pre-update
+---------------------
+
+Triggered just before the repository update is attempted.
+
+.. _addon-event-unit-post-save:
+
+Unit post-save
+--------------
+
+Triggered just after the string is saved.
+
+.. _addon-event-unit-post-sync:
+
+Unit post-sync
+--------------
+
+Triggered after the string is synchronized with the VCS.
+
+.. _addon-event-unit-pre-create:
+
+Unit pre-create
+---------------
+
+Triggered just after the newly created string is saved.
+
+
+.. AUTOGENERATED END: events
diff --git a/docs/snippets/addon-parameters-autogenerated.rst b/docs/snippets/addon-parameters-autogenerated.rst
new file mode 100644
index 000000000000..e4ad24b92ba9
--- /dev/null
+++ b/docs/snippets/addon-parameters-autogenerated.rst
@@ -0,0 +1,392 @@
+
+.. AUTOGENERATED START: addon-parameters
+.. This section is automatically generated by `./manage.py list_addons`. Do not edit manually.
+
+Common add-on parameters
+++++++++++++++++++++++++
+
+.. _addon-choice-engines:
+
+Machine translation engines
+---------------------------
+
+.. list-table:: Available choices:
+ :width: 100%
+
+ * - ``alibaba``
+ - Alibaba
+ * - ``aws``
+ - Amazon Translate
+ * - ``anthropic``
+ - Anthropic
+ * - ``apertium-apy``
+ - Apertium APy
+ * - ``microsoft-translator``
+ - Azure AI Translator
+ * - ``azure-openai``
+ - Azure OpenAI
+ * - ``baidu``
+ - Baidu
+ * - ``cyrtranslit``
+ - CyrTranslit
+ * - ``deepl``
+ - DeepL
+ * - ``glosbe``
+ - Glosbe
+ * - ``google-translate-api-v3``
+ - Google Cloud Translation Advanced
+ * - ``google-translate``
+ - Google Cloud Translation Basic
+ * - ``libretranslate``
+ - LibreTranslate
+ * - ``modernmt``
+ - ModernMT
+ * - ``mymemory``
+ - MyMemory
+ * - ``netease-sight``
+ - Netease Sight
+ * - ``ollama``
+ - Ollama
+ * - ``openai``
+ - OpenAI
+ * - ``sap-translation-hub``
+ - SAP Translation Hub
+ * - ``systran``
+ - Systran
+ * - ``weblate``
+ - Weblate
+ * - ``weblate-translation-memory``
+ - Weblate Translation Memory
+ * - ``yandex``
+ - Yandex
+ * - ``yandex-v2``
+ - Yandex v2
+ * - ``youdao-zhiyun``
+ - Youdao Zhiyun
+ * - ``tmserver``
+ - tmserver
+
+.. _addon-choice-file_format:
+
+File format
+-----------
+
+.. list-table:: Available choices:
+ :width: 100%
+
+ * - ``apple-xliff``
+ - XLIFF 1.2 with Apple extensions
+ * - ``appstore``
+ - App store metadata files
+ * - ``arb``
+ - ARB file
+ * - ``aresource``
+ - Android String Resource
+ * - ``asciidoc``
+ - AsciiDoc file
+ * - ``ass``
+ - Advanced SubStation Alpha subtitle file
+ * - ``catkeys``
+ - Haiku catkeys
+ * - ``cmp-resource``
+ - Compose Multiplatform Resource
+ * - ``csv``
+ - CSV file
+ * - ``csv-multi``
+ - Multivalue CSV file
+ * - ``csv-simple``
+ - Simple CSV file
+ * - ``dokuwiki``
+ - DokuWiki text file
+ * - ``dtd``
+ - DTD file
+ * - ``flatxml``
+ - Flat XML file
+ * - ``fluent``
+ - Fluent file
+ * - ``formatjs``
+ - Format.JS JSON file
+ * - ``go-i18n-json``
+ - go-i18n v1 JSON file
+ * - ``go-i18n-json-v2``
+ - go-i18n v2 JSON file
+ * - ``go-i18n-toml``
+ - go-i18n TOML file
+ * - ``gotext``
+ - gotext JSON file
+ * - ``gwt``
+ - GWT properties
+ * - ``html``
+ - HTML file
+ * - ``i18next``
+ - i18next JSON file v3
+ * - ``i18nextv4``
+ - i18next JSON file v4
+ * - ``idml``
+ - IDML file
+ * - ``ini``
+ - INI file
+ * - ``islu``
+ - Inno Setup INI file
+ * - ``joomla``
+ - Joomla language file
+ * - ``json``
+ - JSON file
+ * - ``json-nested``
+ - JSON nested structure file
+ * - ``laravel``
+ - Laravel PHP strings
+ * - ``markdown``
+ - Markdown file
+ * - ``mediawiki``
+ - MediaWiki text file
+ * - ``mi18n-lang``
+ - @draggable/i18n lang file
+ * - ``moko-resource``
+ - Mobile Kotlin Resource
+ * - ``nextcloud-json``
+ - Nextcloud JSON file
+ * - ``odf``
+ - OpenDocument file
+ * - ``php``
+ - PHP strings
+ * - ``plainxliff``
+ - XLIFF 1.2 translation file
+ * - ``po``
+ - gettext PO file
+ * - ``po-mono``
+ - gettext PO file (monolingual)
+ * - ``poxliff``
+ - XLIFF 1.2 with gettext extensions
+ * - ``properties``
+ - Java Properties
+ * - ``rc``
+ - RC file
+ * - ``resjson``
+ - RESJSON file
+ * - ``resourcedictionary``
+ - ResourceDictionary file
+ * - ``resx``
+ - .NET resource file
+ * - ``ruby-yaml``
+ - Ruby YAML file
+ * - ``srt``
+ - SubRip subtitle file
+ * - ``ssa``
+ - SubStation Alpha subtitle file
+ * - ``strings``
+ - iOS strings
+ * - ``stringsdict``
+ - Stringsdict file
+ * - ``sub``
+ - MicroDVD subtitle file
+ * - ``tbx``
+ - TermBase eXchange file
+ * - ``toml``
+ - TOML file
+ * - ``ts``
+ - Qt Linguist translation file
+ * - ``txt``
+ - Plain text file
+ * - ``webextension``
+ - WebExtension JSON file
+ * - ``wxl``
+ - WixLocalization file
+ * - ``xliff``
+ - XLIFF 1.2 with placeables support
+ * - ``xliff2``
+ - XLIFF 2.0 translation file
+ * - ``xliff2-placeables``
+ - XLIFF 2.0 translation file with placeables support
+ * - ``xlsx``
+ - Excel Open XML
+ * - ``xwiki-fullpage``
+ - XWiki Full Page
+ * - ``xwiki-java-properties``
+ - XWiki Java Properties
+ * - ``xwiki-page-properties``
+ - XWiki Page Properties
+ * - ``yaml``
+ - YAML file
+
+.. _addon-choice-events:
+
+Change events
+-------------
+
+.. list-table:: Available choices:
+ :width: 100%
+
+ * - ``0``
+ - Resource updated
+ * - ``1``
+ - Translation completed
+ * - ``2``
+ - Translation changed
+ * - ``3``
+ - Comment added
+ * - ``4``
+ - Suggestion added
+ * - ``5``
+ - Translation added
+ * - ``6``
+ - Automatically translated
+ * - ``7``
+ - Suggestion accepted
+ * - ``8``
+ - Translation reverted
+ * - ``9``
+ - Translation uploaded
+ * - ``13``
+ - Source string added
+ * - ``14``
+ - Component locked
+ * - ``15``
+ - Component unlocked
+ * - ``17``
+ - Changes committed
+ * - ``18``
+ - Changes pushed
+ * - ``19``
+ - Repository reset
+ * - ``20``
+ - Repository merged
+ * - ``21``
+ - Repository rebased
+ * - ``22``
+ - Repository merge failed
+ * - ``23``
+ - Repository rebase failed
+ * - ``24``
+ - Parsing failed
+ * - ``25``
+ - Translation removed
+ * - ``26``
+ - Suggestion removed
+ * - ``27``
+ - Translation replaced
+ * - ``28``
+ - Repository push failed
+ * - ``29``
+ - Suggestion removed during cleanup
+ * - ``30``
+ - Source string changed
+ * - ``31``
+ - String added
+ * - ``32``
+ - Bulk status changed
+ * - ``33``
+ - Visibility changed
+ * - ``34``
+ - User added
+ * - ``35``
+ - User removed
+ * - ``36``
+ - Translation approved
+ * - ``37``
+ - Marked for edit
+ * - ``38``
+ - Component removed
+ * - ``39``
+ - Project removed
+ * - ``41``
+ - Project renamed
+ * - ``42``
+ - Component renamed
+ * - ``43``
+ - Moved component
+ * - ``45``
+ - Contributor joined
+ * - ``46``
+ - Announcement posted
+ * - ``47``
+ - Alert triggered
+ * - ``48``
+ - Language added
+ * - ``49``
+ - Language requested
+ * - ``50``
+ - Project created
+ * - ``51``
+ - Component created
+ * - ``52``
+ - User invited
+ * - ``53``
+ - Repository notification received
+ * - ``54``
+ - Translation replaced file by upload
+ * - ``55``
+ - License changed
+ * - ``56``
+ - Contributor license agreement changed
+ * - ``57``
+ - Screenshot added
+ * - ``58``
+ - Screenshot uploaded
+ * - ``59``
+ - String updated in the repository
+ * - ``60``
+ - Add-on installed
+ * - ``61``
+ - Add-on configuration changed
+ * - ``62``
+ - Add-on uninstalled
+ * - ``63``
+ - String removed
+ * - ``64``
+ - Comment removed
+ * - ``65``
+ - Comment resolved
+ * - ``66``
+ - Explanation updated
+ * - ``67``
+ - Category removed
+ * - ``68``
+ - Category renamed
+ * - ``69``
+ - Category moved
+ * - ``70``
+ - Saving string failed
+ * - ``71``
+ - String added in the repository
+ * - ``72``
+ - String updated in the upload
+ * - ``73``
+ - String added in the upload
+ * - ``74``
+ - Translation updated by source upload
+ * - ``75``
+ - Component translation completed
+ * - ``76``
+ - Applied enforced check
+ * - ``77``
+ - Propagated change
+ * - ``78``
+ - File uploaded
+ * - ``79``
+ - Extra flags updated
+ * - ``80``
+ - Font uploaded
+ * - ``81``
+ - Font changed
+ * - ``82``
+ - Font removed
+ * - ``83``
+ - Forced synchronization of translations
+ * - ``84``
+ - Forced rescan of translations
+ * - ``85``
+ - Screenshot removed
+ * - ``86``
+ - Label added
+ * - ``87``
+ - Label removed
+ * - ``88``
+ - Repository cleanup
+ * - ``89``
+ - Source string added in the upload
+ * - ``90``
+ - Source string added in the repository
+
+
+.. AUTOGENERATED END: addon-parameters
diff --git a/docs/snippets/addons-autogenerated.rst b/docs/snippets/addons-autogenerated.rst
index dfe026829d61..f940398211c3 100644
--- a/docs/snippets/addons-autogenerated.rst
+++ b/docs/snippets/addons-autogenerated.rst
@@ -1,114 +1,4 @@
-.. AUTOGENERATED START: events
-.. This section is automatically generated by `./manage.py list_addons`. Do not edit manually.
-
-Events that trigger add-ons
-+++++++++++++++++++++++++++
-.. _addon-event-add-on-installation:
-
-Add-on installation
--------------------
-
-Triggered when add-on is being installed.
-
-.. _addon-event-component-update:
-
-Component update
-----------------
-
-Triggered whenever a change happens in a component such as:
-
-* Strings are changed in the repository.
-* A string is added.
-* A new translation is added.
-
-.. _addon-event-daily:
-
-Daily
------
-
-Triggered daily, but add-ons usually split the daily load between components depending on :setting:`BACKGROUND_TASKS`.
-
-.. _addon-event-event-change:
-
-Event change
-------------
-
-Triggered after a Change event is created.
-
-.. _addon-event-repository-post-add:
-
-Repository post-add
--------------------
-
-Triggered just after the new translation is added and committed.
-
-.. _addon-event-repository-post-commit:
-
-Repository post-commit
-----------------------
-
-Triggered just after the changes are committed.
-
-.. _addon-event-repository-post-push:
-
-Repository post-push
---------------------
-
-Triggered just after the repository is pushed upstream.
-
-.. _addon-event-repository-post-update:
-
-Repository post-update
-----------------------
-
-Triggered whenever new changes are pulled from the upstream repository.
-
-.. _addon-event-repository-pre-commit:
-
-Repository pre-commit
----------------------
-
-Triggered just before the changes are committed.
-
-.. _addon-event-repository-pre-push:
-
-Repository pre-push
--------------------
-
-Triggered just before the repository is pushed upstream.
-
-.. _addon-event-repository-pre-update:
-
-Repository pre-update
----------------------
-
-Triggered just before the repository update is attempted.
-
-.. _addon-event-unit-post-save:
-
-Unit post-save
---------------
-
-Triggered just after the string is saved.
-
-.. _addon-event-unit-post-sync:
-
-Unit post-sync
---------------
-
-Triggered after the string is synchronized with the VCS.
-
-.. _addon-event-unit-pre-create:
-
-Unit pre-create
----------------
-
-Triggered just after the newly created string is saved.
-
-
-.. AUTOGENERATED END: events
-
.. AUTOGENERATED START: addons-header
.. This section is automatically generated by `./manage.py list_addons`. Do not edit manually.
@@ -1056,6 +946,38 @@ Customize gettext output
.. versionremoved:: 5.13 Replaced by :ref:`file_format_params`.
+.. AUTOGENERATED START: weblate.gettext.django
+.. This section is automatically generated by `./manage.py list_addons`. Do not edit manually.
+
+.. _addon-weblate.gettext.django:
+
+Update POT file (Django)
+------------------------
+
+.. versionadded:: 5.17
+
+:Add-on ID: ``weblate.gettext.django``
+:Configuration: +----------------------+----------------------+----------------------------------------------------------------------------------+
+ | ``interval`` | Update frequency | How often the add-on should update the POT file when the component is refreshed. |
+ | | | |
+ | | | .. list-table:: Available choices: |
+ | | | :width: 100% |
+ | | | |
+ | | | * - ``daily`` |
+ | | | - Daily |
+ | | | * - ``weekly`` |
+ | | | - Weekly |
+ | | | * - ``monthly`` |
+ | | | - Monthly |
+ +----------------------+----------------------+----------------------------------------------------------------------------------+
+ | ``normalize_header`` | Normalize POT header | Updates gettext headers and replaces placeholder POT comments. |
+ +----------------------+----------------------+----------------------------------------------------------------------------------+
+
+:Triggers: :ref:`addon-event-add-on-installation`, :ref:`addon-event-repository-post-update`
+
+Updates the gettext template using Django's built-in makemessages command.
+
+.. AUTOGENERATED END: weblate.gettext.django
.. AUTOGENERATED START: weblate.gettext.linguas
.. This section is automatically generated by `./manage.py list_addons`. Do not edit manually.
@@ -1071,6 +993,76 @@ Update LINGUAS file
Updates the LINGUAS file when a new translation is added.
.. AUTOGENERATED END: weblate.gettext.linguas
+.. AUTOGENERATED START: weblate.gettext.meson
+.. This section is automatically generated by `./manage.py list_addons`. Do not edit manually.
+
+.. _addon-weblate.gettext.meson:
+
+Update POT file (Meson)
+-----------------------
+
+.. versionadded:: 5.17
+
+:Add-on ID: ``weblate.gettext.meson``
+:Configuration: +----------------------+----------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+
+ | ``interval`` | Update frequency | How often the add-on should update the POT file when the component is refreshed. |
+ | | | |
+ | | | .. list-table:: Available choices: |
+ | | | :width: 100% |
+ | | | |
+ | | | * - ``daily`` |
+ | | | - Daily |
+ | | | * - ``weekly`` |
+ | | | - Weekly |
+ | | | * - ``monthly`` |
+ | | | - Monthly |
+ +----------------------+----------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+
+ | ``normalize_header`` | Normalize POT header | Updates gettext headers and replaces placeholder POT comments. |
+ +----------------------+----------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+
+ | ``comment_mode`` | Code comments | Choose whether xgettext should extract no comments, all comments, or only comments marked with a specific tag. |
+ | | | |
+ | | | .. list-table:: Available choices: |
+ | | | :width: 100% |
+ | | | |
+ | | | * - ``off`` |
+ | | | - Do not extract comments |
+ | | | * - ``all`` |
+ | | | - Extract all comments |
+ | | | * - ``tagged`` |
+ | | | - Extract comments with tag |
+ +----------------------+----------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+
+ | ``comment_tag`` | Comment tag | Tag passed to xgettext for comment extraction when using tagged comment mode. |
+ +----------------------+----------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+
+ | ``checks`` | xgettext checks | Additional xgettext validation checks to enable for extracted messages. |
+ | | | |
+ | | | .. list-table:: Available choices: |
+ | | | :width: 100% |
+ | | | |
+ | | | * - ``ellipsis-unicode`` |
+ | | | - ellipsis-unicode |
+ | | | * - ``space-ellipsis`` |
+ | | | - space-ellipsis |
+ | | | * - ``quote-unicode`` |
+ | | | - quote-unicode |
+ | | | * - ``bullet-unicode`` |
+ | | | - bullet-unicode |
+ +----------------------+----------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+
+ | ``keyword`` | Additional keyword | Optional extra keyword passed to xgettext using --keyword. |
+ +----------------------+----------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+
+ | ``preset`` | Meson preset | Built-in xgettext argument preset matching Meson gettext integration. The GLib preset adds the keyword and format-flag options used by Meson's gettext helper. |
+ | | | |
+ | | | .. list-table:: Available choices: |
+ | | | :width: 100% |
+ | | | |
+ | | | * - ``glib`` |
+ | | | - GLib |
+ +----------------------+----------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+
+
+:Triggers: :ref:`addon-event-add-on-installation`, :ref:`addon-event-repository-post-update`
+
+Updates the gettext template using Meson gettext conventions.
+
+.. AUTOGENERATED END: weblate.gettext.meson
.. AUTOGENERATED START: weblate.gettext.mo
.. This section is automatically generated by `./manage.py list_addons`. Do not edit manually.
@@ -1142,75 +1134,214 @@ configuration.
* :ref:`faq-cleanup`
* :ref:`updating-target-files`
-.. AUTOGENERATED START: weblate.git.squash
+.. AUTOGENERATED START: weblate.gettext.sphinx
.. This section is automatically generated by `./manage.py list_addons`. Do not edit manually.
-.. _addon-weblate.git.squash:
-
-Squash Git commits
-------------------
-
-:Add-on ID: ``weblate.git.squash``
-:Configuration: +---------------------+--------------------------------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------+
- | ``squash`` | Commit squashing | .. list-table:: Available choices: |
- | | | :width: 100% |
- | | | |
- | | | * - ``all`` |
- | | | - All commits into one |
- | | | * - ``language`` |
- | | | - Per language |
- | | | * - ``file`` |
- | | | - Per file |
- | | | * - ``author`` |
- | | | - Per author |
- +---------------------+--------------------------------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------+
- | ``append_trailers`` | Append trailers to squashed commit message | Trailer lines are lines that look similar to RFC 822 e-mail headers, at the end of the otherwise free-form part of a commit message, such as 'Co-authored-by: …'. |
- +---------------------+--------------------------------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------+
- | ``commit_message`` | Commit message | This commit message will be used instead of the combined commit messages from the squashed commits. |
- +---------------------+--------------------------------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------+
-
-:Triggers: :ref:`addon-event-add-on-installation`, :ref:`addon-event-repository-post-commit`
-
-Squash Git commits prior to pushing changes.
-
-.. AUTOGENERATED END: weblate.git.squash
-
-.. hint::
-
- To avoid unnecessary conflicts, it is recommended to configure automatic
- receiving of upstream changes by webhooks or API, see :ref:`update-vcs`.
-
-Git commits can be squashed prior to pushing changes
-in one of the following modes:
+.. _addon-weblate.gettext.sphinx:
-* All commits into one
-* Per language
-* Per file
-* Per author
+Update POT file (Sphinx)
+------------------------
-Original commit messages are kept, but authorship is lost unless :guilabel:`Per author` is selected, or
-the commit message is customized to include it.
+.. versionadded:: 5.17
-The original commit messages can optionally be overridden with a custom commit message.
+:Add-on ID: ``weblate.gettext.sphinx``
+:Configuration: +----------------------+----------------------+-------------------------------------------------------------------------------------+
+ | ``interval`` | Update frequency | How often the add-on should update the POT file when the component is refreshed. |
+ | | | |
+ | | | .. list-table:: Available choices: |
+ | | | :width: 100% |
+ | | | |
+ | | | * - ``daily`` |
+ | | | - Daily |
+ | | | * - ``weekly`` |
+ | | | - Weekly |
+ | | | * - ``monthly`` |
+ | | | - Monthly |
+ +----------------------+----------------------+-------------------------------------------------------------------------------------+
+ | ``normalize_header`` | Normalize POT header | Updates gettext headers and replaces placeholder POT comments. |
+ +----------------------+----------------------+-------------------------------------------------------------------------------------+
+ | ``filter_mode`` | Filtering | Optionally remove strings that are not useful to translate after Sphinx extraction. |
+ | | | |
+ | | | .. list-table:: Available choices: |
+ | | | :width: 100% |
+ | | | |
+ | | | * - ``none`` |
+ | | | - None |
+ | | | * - ``weblate_docs`` |
+ | | | - Weblate documentation |
+ +----------------------+----------------------+-------------------------------------------------------------------------------------+
-Trailers (commit lines like ``Co-authored-by: …``) can optionally be removed
-from the original commit messages and appended to the end of the squashed
-commit message. This also generates proper ``Co-authored-by:`` credit for every
-translator.
+:Triggers: :ref:`addon-event-add-on-installation`, :ref:`addon-event-repository-post-update`
-.. _addon-weblate.json.customize:
+Updates the gettext template using Sphinx's gettext builder without loading
+project configuration.
-Customize JSON output
----------------------
+.. AUTOGENERATED END: weblate.gettext.sphinx
+.. AUTOGENERATED START: weblate.gettext.xgettext
+.. This section is automatically generated by `./manage.py list_addons`. Do not edit manually.
-.. versionchanged:: 5.12
+.. _addon-weblate.gettext.xgettext:
- :guilabel:`Avoid spaces after separators` option added.
+Update POT file (xgettext)
+--------------------------
-.. versionremoved:: 5.13 Replaced by :ref:`file_format_params`.
+.. versionadded:: 5.17
-.. AUTOGENERATED START: weblate.properties.sort
-.. This section is automatically generated by `./manage.py list_addons`. Do not edit manually.
+:Add-on ID: ``weblate.gettext.xgettext``
+:Configuration: +----------------------+----------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
+ | ``interval`` | Update frequency | How often the add-on should update the POT file when the component is refreshed. |
+ | | | |
+ | | | .. list-table:: Available choices: |
+ | | | :width: 100% |
+ | | | |
+ | | | * - ``daily`` |
+ | | | - Daily |
+ | | | * - ``weekly`` |
+ | | | - Weekly |
+ | | | * - ``monthly`` |
+ | | | - Monthly |
+ +----------------------+----------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
+ | ``normalize_header`` | Normalize POT header | Updates gettext headers and replaces placeholder POT comments. |
+ +----------------------+----------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
+ | ``comment_mode`` | Code comments | Choose whether xgettext should extract no comments, all comments, or only comments marked with a specific tag. |
+ | | | |
+ | | | .. list-table:: Available choices: |
+ | | | :width: 100% |
+ | | | |
+ | | | * - ``off`` |
+ | | | - Do not extract comments |
+ | | | * - ``all`` |
+ | | | - Extract all comments |
+ | | | * - ``tagged`` |
+ | | | - Extract comments with tag |
+ +----------------------+----------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
+ | ``comment_tag`` | Comment tag | Tag passed to xgettext for comment extraction when using tagged comment mode. |
+ +----------------------+----------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
+ | ``checks`` | xgettext checks | Additional xgettext validation checks to enable for extracted messages. |
+ | | | |
+ | | | .. list-table:: Available choices: |
+ | | | :width: 100% |
+ | | | |
+ | | | * - ``ellipsis-unicode`` |
+ | | | - ellipsis-unicode |
+ | | | * - ``space-ellipsis`` |
+ | | | - space-ellipsis |
+ | | | * - ``quote-unicode`` |
+ | | | - quote-unicode |
+ | | | * - ``bullet-unicode`` |
+ | | | - bullet-unicode |
+ +----------------------+----------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
+ | ``keyword`` | Additional keyword | Optional extra keyword passed to xgettext using --keyword. |
+ +----------------------+----------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
+ | ``input_mode`` | Input source | Choose whether xgettext should read source files from glob patterns or from a POTFILES/POTFILES.in manifest. |
+ | | | |
+ | | | .. list-table:: Available choices: |
+ | | | :width: 100% |
+ | | | |
+ | | | * - ``patterns`` |
+ | | | - Source file patterns |
+ | | | * - ``potfiles`` |
+ | | | - POTFILES manifest |
+ +----------------------+----------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
+ | ``language`` | xgettext language | Programming language passed to xgettext, for example Python or C. |
+ +----------------------+----------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
+ | ``source_patterns`` | Source file patterns | Newline-separated repository-relative glob patterns for files to extract with xgettext. |
+ +----------------------+----------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
+ | ``potfiles_path`` | POTFILES path | Repository-relative path to POTFILES or POTFILES.in. Entries are resolved relative to the repository root. If present next to the manifest, POTFILES.skip excludes listed files from extraction. |
+ +----------------------+----------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
+
+:Triggers: :ref:`addon-event-add-on-installation`, :ref:`addon-event-repository-post-update`
+
+Updates the gettext template using xgettext on selected source files.
+
+.. AUTOGENERATED END: weblate.gettext.xgettext
+
+.. AUTOGENERATED START: weblate.git.squash
+.. This section is automatically generated by `./manage.py list_addons`. Do not edit manually.
+
+.. _addon-weblate.git.squash:
+
+Squash Git commits
+------------------
+
+:Add-on ID: ``weblate.git.squash``
+:Configuration: +---------------------+--------------------------------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------+
+ | ``squash`` | Commit squashing | .. list-table:: Available choices: |
+ | | | :width: 100% |
+ | | | |
+ | | | * - ``all`` |
+ | | | - All commits into one |
+ | | | * - ``language`` |
+ | | | - Per language |
+ | | | * - ``file`` |
+ | | | - Per file |
+ | | | * - ``author`` |
+ | | | - Per author |
+ +---------------------+--------------------------------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------+
+ | ``append_trailers`` | Append trailers to squashed commit message | Trailer lines are lines that look similar to RFC 822 e-mail headers, at the end of the otherwise free-form part of a commit message, such as 'Co-authored-by: …'. |
+ +---------------------+--------------------------------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------+
+ | ``commit_message`` | Commit message | This commit message will be used instead of the combined commit messages from the squashed commits. |
+ +---------------------+--------------------------------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------+
+
+:Triggers: :ref:`addon-event-add-on-installation`, :ref:`addon-event-repository-post-commit`
+
+Squash Git commits prior to pushing changes.
+
+.. AUTOGENERATED END: weblate.git.squash
+
+.. hint::
+
+ To avoid unnecessary conflicts, it is recommended to configure automatic
+ receiving of upstream changes by webhooks or API, see :ref:`update-vcs`.
+
+Git commits can be squashed prior to pushing changes
+in one of the following modes:
+
+* All commits into one
+* Per language
+* Per file
+* Per author
+
+Original commit messages are kept, but authorship is lost unless :guilabel:`Per author` is selected, or
+the commit message is customized to include it.
+
+The original commit messages can optionally be overridden with a custom commit message.
+
+Trailers (commit lines like ``Co-authored-by: …``) can optionally be removed
+from the original commit messages and appended to the end of the squashed
+commit message. This also generates proper ``Co-authored-by:`` credit for every
+translator.
+
+.. _addon-weblate.json.customize:
+
+Customize JSON output
+---------------------
+
+.. versionchanged:: 5.12
+
+ :guilabel:`Avoid spaces after separators` option added.
+
+.. versionremoved:: 5.13 Replaced by :ref:`file_format_params`.
+
+.. AUTOGENERATED START: weblate.hosted.reset
+.. This section is automatically generated by `./manage.py list_addons`. Do not edit manually.
+
+.. _addon-weblate.hosted.reset:
+
+Reset repository to upstream
+----------------------------
+
+.. versionadded:: 5.17
+
+:Add-on ID: ``weblate.hosted.reset``
+:Configuration: `This add-on has no configuration.`
+:Triggers: :ref:`addon-event-add-on-installation`, :ref:`addon-event-daily`
+
+Discards all changes in the Weblate repository each night.
+
+.. AUTOGENERATED END: weblate.hosted.reset
+.. AUTOGENERATED START: weblate.properties.sort
+.. This section is automatically generated by `./manage.py list_addons`. Do not edit manually.
.. _addon-weblate.properties.sort:
@@ -1438,654 +1569,3 @@ or an implementation of the "Standard Webhooks Specification".
* `Standard Webhooks Specification `_
* :ref:`schema-messaging`
* `Python library for Standard Webhooks `_
-
-.. AUTOGENERATED START: weblate.hosted.reset
-.. This section is automatically generated by `./manage.py list_addons`. Do not edit manually.
-
-.. _addon-weblate.hosted.reset:
-
-Reset repository to upstream
-----------------------------
-
-.. versionadded:: 5.17
-
-:Add-on ID: ``weblate.hosted.reset``
-:Configuration: `This add-on has no configuration.`
-:Triggers: :ref:`addon-event-add-on-installation`, :ref:`addon-event-daily`
-
-Discards all changes in the Weblate repository each night.
-
-.. AUTOGENERATED END: weblate.hosted.reset
-.. AUTOGENERATED START: weblate.gettext.django
-.. This section is automatically generated by `./manage.py list_addons`. Do not edit manually.
-
-.. _addon-weblate.gettext.django:
-
-Update POT file (Django)
-------------------------
-
-.. versionadded:: 5.17
-
-:Add-on ID: ``weblate.gettext.django``
-:Configuration: +----------------------+----------------------+----------------------------------------------------------------------------------+
- | ``interval`` | Update frequency | How often the add-on should update the POT file when the component is refreshed. |
- | | | |
- | | | .. list-table:: Available choices: |
- | | | :width: 100% |
- | | | |
- | | | * - ``daily`` |
- | | | - Daily |
- | | | * - ``weekly`` |
- | | | - Weekly |
- | | | * - ``monthly`` |
- | | | - Monthly |
- +----------------------+----------------------+----------------------------------------------------------------------------------+
- | ``normalize_header`` | Normalize POT header | Updates gettext headers and replaces placeholder POT comments. |
- +----------------------+----------------------+----------------------------------------------------------------------------------+
-
-:Triggers: :ref:`addon-event-add-on-installation`, :ref:`addon-event-repository-post-update`
-
-Updates the gettext template using Django's built-in makemessages command.
-
-.. AUTOGENERATED END: weblate.gettext.django
-.. AUTOGENERATED START: weblate.gettext.meson
-.. This section is automatically generated by `./manage.py list_addons`. Do not edit manually.
-
-.. _addon-weblate.gettext.meson:
-
-Update POT file (Meson)
------------------------
-
-.. versionadded:: 5.17
-
-:Add-on ID: ``weblate.gettext.meson``
-:Configuration: +----------------------+----------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+
- | ``interval`` | Update frequency | How often the add-on should update the POT file when the component is refreshed. |
- | | | |
- | | | .. list-table:: Available choices: |
- | | | :width: 100% |
- | | | |
- | | | * - ``daily`` |
- | | | - Daily |
- | | | * - ``weekly`` |
- | | | - Weekly |
- | | | * - ``monthly`` |
- | | | - Monthly |
- +----------------------+----------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+
- | ``normalize_header`` | Normalize POT header | Updates gettext headers and replaces placeholder POT comments. |
- +----------------------+----------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+
- | ``comment_mode`` | Code comments | Choose whether xgettext should extract no comments, all comments, or only comments marked with a specific tag. |
- | | | |
- | | | .. list-table:: Available choices: |
- | | | :width: 100% |
- | | | |
- | | | * - ``off`` |
- | | | - Do not extract comments |
- | | | * - ``all`` |
- | | | - Extract all comments |
- | | | * - ``tagged`` |
- | | | - Extract comments with tag |
- +----------------------+----------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+
- | ``comment_tag`` | Comment tag | Tag passed to xgettext for comment extraction when using tagged comment mode. |
- +----------------------+----------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+
- | ``checks`` | xgettext checks | Additional xgettext validation checks to enable for extracted messages. |
- | | | |
- | | | .. list-table:: Available choices: |
- | | | :width: 100% |
- | | | |
- | | | * - ``ellipsis-unicode`` |
- | | | - ellipsis-unicode |
- | | | * - ``space-ellipsis`` |
- | | | - space-ellipsis |
- | | | * - ``quote-unicode`` |
- | | | - quote-unicode |
- | | | * - ``bullet-unicode`` |
- | | | - bullet-unicode |
- +----------------------+----------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+
- | ``keyword`` | Additional keyword | Optional extra keyword passed to xgettext using --keyword. |
- +----------------------+----------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+
- | ``preset`` | Meson preset | Built-in xgettext argument preset matching Meson gettext integration. The GLib preset adds the keyword and format-flag options used by Meson's gettext helper. |
- | | | |
- | | | .. list-table:: Available choices: |
- | | | :width: 100% |
- | | | |
- | | | * - ``glib`` |
- | | | - GLib |
- +----------------------+----------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+
-
-:Triggers: :ref:`addon-event-add-on-installation`, :ref:`addon-event-repository-post-update`
-
-Updates the gettext template using Meson gettext conventions.
-
-.. AUTOGENERATED END: weblate.gettext.meson
-.. AUTOGENERATED START: weblate.gettext.sphinx
-.. This section is automatically generated by `./manage.py list_addons`. Do not edit manually.
-
-.. _addon-weblate.gettext.sphinx:
-
-Update POT file (Sphinx)
-------------------------
-
-.. versionadded:: 5.17
-
-:Add-on ID: ``weblate.gettext.sphinx``
-:Configuration: +----------------------+----------------------+-------------------------------------------------------------------------------------+
- | ``interval`` | Update frequency | How often the add-on should update the POT file when the component is refreshed. |
- | | | |
- | | | .. list-table:: Available choices: |
- | | | :width: 100% |
- | | | |
- | | | * - ``daily`` |
- | | | - Daily |
- | | | * - ``weekly`` |
- | | | - Weekly |
- | | | * - ``monthly`` |
- | | | - Monthly |
- +----------------------+----------------------+-------------------------------------------------------------------------------------+
- | ``normalize_header`` | Normalize POT header | Updates gettext headers and replaces placeholder POT comments. |
- +----------------------+----------------------+-------------------------------------------------------------------------------------+
- | ``filter_mode`` | Filtering | Optionally remove strings that are not useful to translate after Sphinx extraction. |
- | | | |
- | | | .. list-table:: Available choices: |
- | | | :width: 100% |
- | | | |
- | | | * - ``none`` |
- | | | - None |
- | | | * - ``weblate_docs`` |
- | | | - Weblate documentation |
- +----------------------+----------------------+-------------------------------------------------------------------------------------+
-
-:Triggers: :ref:`addon-event-add-on-installation`, :ref:`addon-event-repository-post-update`
-
-Updates the gettext template using Sphinx's gettext builder without loading
-project configuration.
-
-.. AUTOGENERATED END: weblate.gettext.sphinx
-.. AUTOGENERATED START: weblate.gettext.xgettext
-.. This section is automatically generated by `./manage.py list_addons`. Do not edit manually.
-
-.. _addon-weblate.gettext.xgettext:
-
-Update POT file (xgettext)
---------------------------
-
-.. versionadded:: 5.17
-
-:Add-on ID: ``weblate.gettext.xgettext``
-:Configuration: +----------------------+----------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
- | ``interval`` | Update frequency | How often the add-on should update the POT file when the component is refreshed. |
- | | | |
- | | | .. list-table:: Available choices: |
- | | | :width: 100% |
- | | | |
- | | | * - ``daily`` |
- | | | - Daily |
- | | | * - ``weekly`` |
- | | | - Weekly |
- | | | * - ``monthly`` |
- | | | - Monthly |
- +----------------------+----------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
- | ``normalize_header`` | Normalize POT header | Updates gettext headers and replaces placeholder POT comments. |
- +----------------------+----------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
- | ``comment_mode`` | Code comments | Choose whether xgettext should extract no comments, all comments, or only comments marked with a specific tag. |
- | | | |
- | | | .. list-table:: Available choices: |
- | | | :width: 100% |
- | | | |
- | | | * - ``off`` |
- | | | - Do not extract comments |
- | | | * - ``all`` |
- | | | - Extract all comments |
- | | | * - ``tagged`` |
- | | | - Extract comments with tag |
- +----------------------+----------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
- | ``comment_tag`` | Comment tag | Tag passed to xgettext for comment extraction when using tagged comment mode. |
- +----------------------+----------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
- | ``checks`` | xgettext checks | Additional xgettext validation checks to enable for extracted messages. |
- | | | |
- | | | .. list-table:: Available choices: |
- | | | :width: 100% |
- | | | |
- | | | * - ``ellipsis-unicode`` |
- | | | - ellipsis-unicode |
- | | | * - ``space-ellipsis`` |
- | | | - space-ellipsis |
- | | | * - ``quote-unicode`` |
- | | | - quote-unicode |
- | | | * - ``bullet-unicode`` |
- | | | - bullet-unicode |
- +----------------------+----------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
- | ``keyword`` | Additional keyword | Optional extra keyword passed to xgettext using --keyword. |
- +----------------------+----------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
- | ``input_mode`` | Input source | Choose whether xgettext should read source files from glob patterns or from a POTFILES/POTFILES.in manifest. |
- | | | |
- | | | .. list-table:: Available choices: |
- | | | :width: 100% |
- | | | |
- | | | * - ``patterns`` |
- | | | - Source file patterns |
- | | | * - ``potfiles`` |
- | | | - POTFILES manifest |
- +----------------------+----------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
- | ``language`` | xgettext language | Programming language passed to xgettext, for example Python or C. |
- +----------------------+----------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
- | ``source_patterns`` | Source file patterns | Newline-separated repository-relative glob patterns for files to extract with xgettext. |
- +----------------------+----------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
- | ``potfiles_path`` | POTFILES path | Repository-relative path to POTFILES or POTFILES.in. Entries are resolved relative to the repository root. If present next to the manifest, POTFILES.skip excludes listed files from extraction. |
- +----------------------+----------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
-
-:Triggers: :ref:`addon-event-add-on-installation`, :ref:`addon-event-repository-post-update`
-
-Updates the gettext template using xgettext on selected source files.
-
-.. AUTOGENERATED END: weblate.gettext.xgettext
-
-
-.. Depreciated Addons
-.. _addon-weblate.xml.customize:
-
-Customize XML output
---------------------
-
-.. versionadded:: 4.15
-
-.. versionremoved:: 5.13 Replaced by :ref:`file_format_params`.
-
-.. _addon-weblate.yaml.customize:
-
-Customize YAML output
----------------------
-
-.. versionremoved:: 5.13 Replaced by :ref:`file_format_params`.
-
-Common add-on parameters
-++++++++++++++++++++++++
-
-.. AUTOGENERATED START: addon-parameters
-.. This section is automatically generated by `./manage.py list_addons`. Do not edit manually.
-
-.. _addon-choice-engines:
-
-Machine translation engines
----------------------------
-
-.. list-table:: Available choices:
- :width: 100%
-
- * - ``alibaba``
- - Alibaba
- * - ``aws``
- - Amazon Translate
- * - ``anthropic``
- - Anthropic
- * - ``apertium-apy``
- - Apertium APy
- * - ``microsoft-translator``
- - Azure AI Translator
- * - ``azure-openai``
- - Azure OpenAI
- * - ``baidu``
- - Baidu
- * - ``cyrtranslit``
- - CyrTranslit
- * - ``deepl``
- - DeepL
- * - ``glosbe``
- - Glosbe
- * - ``google-translate-api-v3``
- - Google Cloud Translation Advanced
- * - ``google-translate``
- - Google Cloud Translation Basic
- * - ``libretranslate``
- - LibreTranslate
- * - ``modernmt``
- - ModernMT
- * - ``mymemory``
- - MyMemory
- * - ``netease-sight``
- - Netease Sight
- * - ``ollama``
- - Ollama
- * - ``openai``
- - OpenAI
- * - ``sap-translation-hub``
- - SAP Translation Hub
- * - ``systran``
- - Systran
- * - ``weblate``
- - Weblate
- * - ``weblate-translation-memory``
- - Weblate Translation Memory
- * - ``yandex``
- - Yandex
- * - ``yandex-v2``
- - Yandex v2
- * - ``youdao-zhiyun``
- - Youdao Zhiyun
- * - ``tmserver``
- - tmserver
-
-.. _addon-choice-file_format:
-
-File format
------------
-
-.. list-table:: Available choices:
- :width: 100%
-
- * - ``apple-xliff``
- - XLIFF 1.2 with Apple extensions
- * - ``appstore``
- - App store metadata files
- * - ``arb``
- - ARB file
- * - ``aresource``
- - Android String Resource
- * - ``asciidoc``
- - AsciiDoc file
- * - ``ass``
- - Advanced SubStation Alpha subtitle file
- * - ``catkeys``
- - Haiku catkeys
- * - ``cmp-resource``
- - Compose Multiplatform Resource
- * - ``csv``
- - CSV file
- * - ``csv-multi``
- - Multivalue CSV file
- * - ``csv-simple``
- - Simple CSV file
- * - ``dokuwiki``
- - DokuWiki text file
- * - ``dtd``
- - DTD file
- * - ``flatxml``
- - Flat XML file
- * - ``fluent``
- - Fluent file
- * - ``formatjs``
- - Format.JS JSON file
- * - ``go-i18n-json``
- - go-i18n v1 JSON file
- * - ``go-i18n-json-v2``
- - go-i18n v2 JSON file
- * - ``go-i18n-toml``
- - go-i18n TOML file
- * - ``gotext``
- - gotext JSON file
- * - ``gwt``
- - GWT properties
- * - ``html``
- - HTML file
- * - ``i18next``
- - i18next JSON file v3
- * - ``i18nextv4``
- - i18next JSON file v4
- * - ``idml``
- - IDML file
- * - ``ini``
- - INI file
- * - ``islu``
- - Inno Setup INI file
- * - ``joomla``
- - Joomla language file
- * - ``json``
- - JSON file
- * - ``json-nested``
- - JSON nested structure file
- * - ``laravel``
- - Laravel PHP strings
- * - ``markdown``
- - Markdown file
- * - ``mediawiki``
- - MediaWiki text file
- * - ``mi18n-lang``
- - @draggable/i18n lang file
- * - ``moko-resource``
- - Mobile Kotlin Resource
- * - ``nextcloud-json``
- - Nextcloud JSON file
- * - ``odf``
- - OpenDocument file
- * - ``php``
- - PHP strings
- * - ``plainxliff``
- - XLIFF 1.2 translation file
- * - ``po``
- - gettext PO file
- * - ``po-mono``
- - gettext PO file (monolingual)
- * - ``poxliff``
- - XLIFF 1.2 with gettext extensions
- * - ``properties``
- - Java Properties
- * - ``rc``
- - RC file
- * - ``resjson``
- - RESJSON file
- * - ``resourcedictionary``
- - ResourceDictionary file
- * - ``resx``
- - .NET resource file
- * - ``ruby-yaml``
- - Ruby YAML file
- * - ``srt``
- - SubRip subtitle file
- * - ``ssa``
- - SubStation Alpha subtitle file
- * - ``strings``
- - iOS strings
- * - ``stringsdict``
- - Stringsdict file
- * - ``sub``
- - MicroDVD subtitle file
- * - ``tbx``
- - TermBase eXchange file
- * - ``toml``
- - TOML file
- * - ``ts``
- - Qt Linguist translation file
- * - ``txt``
- - Plain text file
- * - ``webextension``
- - WebExtension JSON file
- * - ``wxl``
- - WixLocalization file
- * - ``xliff``
- - XLIFF 1.2 with placeables support
- * - ``xliff2``
- - XLIFF 2.0 translation file
- * - ``xliff2-placeables``
- - XLIFF 2.0 translation file with placeables support
- * - ``xlsx``
- - Excel Open XML
- * - ``xwiki-fullpage``
- - XWiki Full Page
- * - ``xwiki-java-properties``
- - XWiki Java Properties
- * - ``xwiki-page-properties``
- - XWiki Page Properties
- * - ``yaml``
- - YAML file
-
-.. _addon-choice-events:
-
-Change events
--------------
-
-.. list-table:: Available choices:
- :width: 100%
-
- * - ``0``
- - Resource updated
- * - ``1``
- - Translation completed
- * - ``2``
- - Translation changed
- * - ``3``
- - Comment added
- * - ``4``
- - Suggestion added
- * - ``5``
- - Translation added
- * - ``6``
- - Automatically translated
- * - ``7``
- - Suggestion accepted
- * - ``8``
- - Translation reverted
- * - ``9``
- - Translation uploaded
- * - ``13``
- - Source string added
- * - ``14``
- - Component locked
- * - ``15``
- - Component unlocked
- * - ``17``
- - Changes committed
- * - ``18``
- - Changes pushed
- * - ``19``
- - Repository reset
- * - ``20``
- - Repository merged
- * - ``21``
- - Repository rebased
- * - ``22``
- - Repository merge failed
- * - ``23``
- - Repository rebase failed
- * - ``24``
- - Parsing failed
- * - ``25``
- - Translation removed
- * - ``26``
- - Suggestion removed
- * - ``27``
- - Translation replaced
- * - ``28``
- - Repository push failed
- * - ``29``
- - Suggestion removed during cleanup
- * - ``30``
- - Source string changed
- * - ``31``
- - String added
- * - ``32``
- - Bulk status changed
- * - ``33``
- - Visibility changed
- * - ``34``
- - User added
- * - ``35``
- - User removed
- * - ``36``
- - Translation approved
- * - ``37``
- - Marked for edit
- * - ``38``
- - Component removed
- * - ``39``
- - Project removed
- * - ``41``
- - Project renamed
- * - ``42``
- - Component renamed
- * - ``43``
- - Moved component
- * - ``45``
- - Contributor joined
- * - ``46``
- - Announcement posted
- * - ``47``
- - Alert triggered
- * - ``48``
- - Language added
- * - ``49``
- - Language requested
- * - ``50``
- - Project created
- * - ``51``
- - Component created
- * - ``52``
- - User invited
- * - ``53``
- - Repository notification received
- * - ``54``
- - Translation replaced file by upload
- * - ``55``
- - License changed
- * - ``56``
- - Contributor license agreement changed
- * - ``57``
- - Screenshot added
- * - ``58``
- - Screenshot uploaded
- * - ``59``
- - String updated in the repository
- * - ``60``
- - Add-on installed
- * - ``61``
- - Add-on configuration changed
- * - ``62``
- - Add-on uninstalled
- * - ``63``
- - String removed
- * - ``64``
- - Comment removed
- * - ``65``
- - Comment resolved
- * - ``66``
- - Explanation updated
- * - ``67``
- - Category removed
- * - ``68``
- - Category renamed
- * - ``69``
- - Category moved
- * - ``70``
- - Saving string failed
- * - ``71``
- - String added in the repository
- * - ``72``
- - String updated in the upload
- * - ``73``
- - String added in the upload
- * - ``74``
- - Translation updated by source upload
- * - ``75``
- - Component translation completed
- * - ``76``
- - Applied enforced check
- * - ``77``
- - Propagated change
- * - ``78``
- - File uploaded
- * - ``79``
- - Extra flags updated
- * - ``80``
- - Font uploaded
- * - ``81``
- - Font changed
- * - ``82``
- - Font removed
- * - ``83``
- - Forced synchronization of translations
- * - ``84``
- - Forced rescan of translations
- * - ``85``
- - Screenshot removed
- * - ``86``
- - Label added
- * - ``87``
- - Label removed
- * - ``88``
- - Repository cleanup
- * - ``89``
- - Source string added in the upload
- * - ``90``
- - Source string added in the repository
-
-
-.. AUTOGENERATED END: addon-parameters
diff --git a/weblate/addons/management/commands/list_addons.py b/weblate/addons/management/commands/list_addons.py
index 1ce872344bb6..3ee0196f18b9 100644
--- a/weblate/addons/management/commands/list_addons.py
+++ b/weblate/addons/management/commands/list_addons.py
@@ -38,21 +38,39 @@ def sorted_events(events: Iterable[AddonEvent]) -> Iterable[AddonEvent]:
return sorted(events, key=lambda event: event.label)
-SHARED_PARAMS = {"engines", "file_format", "events"}
+SHARED_PARAMS = ("engines", "file_format", "events")
class Command(DocGeneratorCommand):
help = "List installed add-ons"
+ def add_arguments(self, parser):
+ super().add_arguments(parser)
+ parser.add_argument(
+ "-s",
+ "--sections",
+ nargs="*",
+ choices=["events", "addons", "parameters"],
+ help="Filter output by section. Can specify multiple sections. "
+ "If not specified, all sections are shown.",
+ )
+
def handle(self, *args, **options) -> None:
"""List installed add-ons."""
+ self.sections.clear()
+
# Shared parameters
- self.params = SHARED_PARAMS.copy()
+ self.params = set(SHARED_PARAMS)
self.param_docs: dict[str, list[str]] = {}
+ sections = set(options.get("sections", []) or [])
+ show_all = not sections
- self.generate_events_doc()
- self.generate_addons_doc()
- self.generate_addon_parameters_doc()
+ if show_all or "events" in sections:
+ self.generate_events_doc()
+ if show_all or {"addons", "parameters"} & sections:
+ self.generate_addons_doc(include_sections=show_all or "addons" in sections)
+ if show_all or "parameters" in sections:
+ self.generate_addon_parameters_doc()
self.write_sections(options.get("output"))
@@ -78,11 +96,12 @@ def generate_events_doc(self) -> None:
content.append("")
self.add_section("events", content)
- def generate_addons_doc(self) -> None:
- self.add_section(
- "addons-header",
- ["Built-in add-ons", "++++++++++++++++"],
- )
+ def generate_addons_doc(self, *, include_sections: bool) -> None:
+ if include_sections:
+ self.add_section(
+ "addons-header",
+ ["Built-in add-ons", "++++++++++++++++"],
+ )
fake_addon = Addon(component=Component(project=Project(pk=-1), pk=-1))
for addon_name, obj in sorted(ADDONS.items()):
@@ -169,12 +188,22 @@ def generate_addons_doc(self) -> None:
"\n".join(wrap(str(obj.description), 79)),
]
)
- self.add_section(addon_name, addon_lines)
+ if include_sections:
+ self.add_section(addon_name, addon_lines)
def generate_addon_parameters_doc(self) -> None:
self.add_section(
"addon-parameters",
- ["\n".join(items) for items in self.param_docs.values()],
+ [
+ "Common add-on parameters",
+ "++++++++++++++++++++++++",
+ "",
+ *[
+ "\n".join(self.param_docs[name])
+ for name in SHARED_PARAMS
+ if name in self.param_docs
+ ],
+ ],
)
def get_documented_field(self, form_class, name: str, field):
diff --git a/weblate/addons/tests.py b/weblate/addons/tests.py
index 458c878ce4e6..beeb35802613 100644
--- a/weblate/addons/tests.py
+++ b/weblate/addons/tests.py
@@ -5404,6 +5404,66 @@ def test_list_addons(self) -> None:
output = StringIO()
call_command("list_addons", stdout=output)
self.assertIn(".. _addon-event-add-on-installation:", output.getvalue())
+ self.assertIn("Common add-on parameters", output.getvalue())
with self.assertRaises(FileNotFoundError):
call_command("list_addons", "-o", "missing_fileXXX.rst", stdout=StringIO())
+
+ def test_list_addons_split_output(self) -> None:
+ with (
+ tempfile.NamedTemporaryFile(suffix=".rst", delete=False) as events_handle,
+ tempfile.NamedTemporaryFile(suffix=".rst", delete=False) as addons_handle,
+ tempfile.NamedTemporaryFile(
+ suffix=".rst", delete=False
+ ) as parameters_handle,
+ ):
+ events_path = Path(events_handle.name)
+ addons_path = Path(addons_handle.name)
+ parameters_path = Path(parameters_handle.name)
+ self.addCleanup(events_path.unlink)
+ self.addCleanup(addons_path.unlink)
+ self.addCleanup(parameters_path.unlink)
+
+ call_command(
+ "list_addons",
+ "--sections",
+ "events",
+ "-o",
+ events_path,
+ stdout=StringIO(),
+ )
+ call_command(
+ "list_addons",
+ "--sections",
+ "addons",
+ "-o",
+ addons_path,
+ stdout=StringIO(),
+ )
+ call_command(
+ "list_addons",
+ "--sections",
+ "parameters",
+ "-o",
+ parameters_path,
+ stdout=StringIO(),
+ )
+
+ events_content = events_path.read_text(encoding="utf-8")
+ addons_content = addons_path.read_text(encoding="utf-8")
+ parameters_content = parameters_path.read_text(encoding="utf-8")
+
+ self.assertIn("Events that trigger add-ons", events_content)
+ self.assertIn(".. _addon-event-add-on-installation:", events_content)
+ self.assertNotIn("Built-in add-ons", events_content)
+ self.assertNotIn("Common add-on parameters", events_content)
+
+ self.assertIn("Built-in add-ons", addons_content)
+ self.assertNotIn(".. _addon-event-add-on-installation:", addons_content)
+ self.assertNotIn("Common add-on parameters", addons_content)
+ self.assertNotIn("Customize XML output", addons_content)
+
+ self.assertIn("Common add-on parameters", parameters_content)
+ self.assertIn(".. _addon-choice-engines:", parameters_content)
+ self.assertNotIn("Built-in add-ons", parameters_content)
+ self.assertNotIn("Events that trigger add-ons", parameters_content)
diff --git a/weblate/checks/management/commands/list_checks.py b/weblate/checks/management/commands/list_checks.py
index 4f94dcf1464a..4f1ca4e1332a 100644
--- a/weblate/checks/management/commands/list_checks.py
+++ b/weblate/checks/management/commands/list_checks.py
@@ -7,6 +7,8 @@
from collections import defaultdict
from typing import TYPE_CHECKING
+from django.core.management.base import CommandError
+
from weblate.checks.format import BaseFormatCheck
from weblate.checks.models import CHECKS
from weblate.formats.models import FILE_FORMATS
@@ -84,7 +86,7 @@ def build_check_section(self, check) -> list[str]:
)
)
if check.default_disabled:
- enable_flags: list[str] = {
+ enable_flags: set[str] = {
check.enable_string,
*check.extra_enable_strings,
}
@@ -128,6 +130,13 @@ def handle(self, *args, **options) -> None:
output_file = options.get("output")
+ if output_file is not None and len(sections) != 1:
+ msg = (
+ "Using --output with list_checks requires exactly one "
+ "--sections value to select which generated snippet to write."
+ )
+ raise CommandError(msg)
+
if show_all or "checks" in sections:
self.write_sections(output_file)
diff --git a/weblate/checks/tests/test_commands.py b/weblate/checks/tests/test_commands.py
index c8687b031698..7138ec538762 100644
--- a/weblate/checks/tests/test_commands.py
+++ b/weblate/checks/tests/test_commands.py
@@ -7,6 +7,7 @@
from io import StringIO
from django.core.management import call_command
+from django.core.management.base import CommandError
from django.test import SimpleTestCase
from weblate.trans.tests.test_commands import WeblateComponentCommandTestCase
@@ -34,3 +35,13 @@ def test_list_checks(self) -> None:
output = StringIO()
call_command("list_checks", stdout=output)
self.assertIn(".. _check-same:", output.getvalue())
+
+ def test_list_checks_requires_sections_with_output(self) -> None:
+ with self.assertRaisesRegex(CommandError, "requires exactly one"):
+ call_command("list_checks", "-o", "checks.rst")
+
+ def test_list_checks_requires_single_section_with_output(self) -> None:
+ with self.assertRaisesRegex(CommandError, "requires exactly one"):
+ call_command(
+ "list_checks", "--sections", "checks", "flags", "-o", "checks.rst"
+ )
diff --git a/weblate/utils/management/base.py b/weblate/utils/management/base.py
index cde53287833a..a15e3dc0a82b 100644
--- a/weblate/utils/management/base.py
+++ b/weblate/utils/management/base.py
@@ -227,9 +227,14 @@ def handle(self, *args, **options) -> None:
class DocGeneratorCommand(BaseCommand):
- sections: ClassVar[list[tuple[str, list[str]]]] = []
+ autogenerated_start_prefix: ClassVar[str] = ".. AUTOGENERATED START: "
+ autogenerated_end_prefix: ClassVar[str] = ".. AUTOGENERATED END: "
output_required: bool = False
+ def __init__(self, *args, **kwargs) -> None:
+ super().__init__(*args, **kwargs)
+ self.sections: list[tuple[str, list[str]]] = []
+
def add_arguments(self, parser):
parser.add_argument(
"-o",
@@ -243,8 +248,8 @@ def add_arguments(self, parser):
def autogenerated_markers(self, name: str) -> tuple[str, str]:
return (
- f".. AUTOGENERATED START: {name}",
- f".. AUTOGENERATED END: {name}",
+ f"{self.autogenerated_start_prefix}{name}",
+ f"{self.autogenerated_end_prefix}{name}",
)
def insert_markers(
@@ -278,19 +283,88 @@ def insert_content_in_lines(
def add_section(self, section_id: str, lines: list[str]) -> None:
self.sections.append((section_id, lines))
+ def get_section_id_from_start_marker(self, line: str) -> str:
+ section_id = line.removeprefix(self.autogenerated_start_prefix).strip()
+ if not section_id:
+ msg = f"Invalid autogenerated start marker: {line!r}"
+ raise CommandError(msg)
+ return section_id
+
+ def find_existing_section_end(
+ self,
+ lines: list[str],
+ start_index: int,
+ end_limit: int,
+ section_id: str,
+ source: Path | None = None,
+ ) -> int:
+ end_marker = self.autogenerated_markers(section_id)[1]
+
+ for index in range(start_index, end_limit):
+ if lines[index].rstrip() == end_marker:
+ return index
+
+ source_info = "" if source is None else f" in {source}"
+ msg = (
+ f"Missing autogenerated end marker {end_marker!r} "
+ f"for section {section_id!r}{source_info}"
+ )
+ raise CommandError(msg)
+
+ def extract_existing_sections(
+ self, lines: list[str], source: Path | None = None
+ ) -> tuple[list[str], dict[str, list[str]]]:
+ section_starts = [
+ index
+ for index, line in enumerate(lines)
+ if line.startswith(self.autogenerated_start_prefix)
+ ]
+ if not section_starts:
+ return lines, {}
+
+ preamble = lines[: section_starts[0]]
+ manual_tails: dict[str, list[str]] = {}
+
+ for position, start_index in enumerate(section_starts):
+ section_id = self.get_section_id_from_start_marker(
+ lines[start_index].rstrip()
+ )
+ next_start = (
+ section_starts[position + 1]
+ if position + 1 < len(section_starts)
+ else len(lines)
+ )
+ end_index = self.find_existing_section_end(
+ lines,
+ start_index + 1,
+ next_start,
+ section_id,
+ source,
+ )
+ manual_tails[section_id] = lines[end_index + 1 : next_start]
+
+ return preamble, manual_tails
+
+ def merge_sections(self, lines: list[str], source: Path | None = None) -> list[str]:
+ preamble, manual_tails = self.extract_existing_sections(lines, source)
+ merged_lines = [*preamble]
+
+ for section_id, section_lines in self.sections:
+ start_marker, end_marker = self.autogenerated_markers(section_id)
+ merged_lines.extend(
+ self.insert_markers(section_lines, start_marker, end_marker)
+ )
+ merged_lines.extend(manual_tails.get(section_id, []))
+
+ return merged_lines
+
def write_sections(self, output_file: Path | None) -> None:
if output_file:
lines = output_file.read_text(encoding="utf-8").splitlines()
- for section_id, section_lines in self.sections:
- start_marker, end_marker = self.autogenerated_markers(section_id)
- block = self.insert_markers(section_lines, start_marker, end_marker)
- lines = self.insert_content_in_lines(
- block,
- lines,
- start_marker,
- end_marker,
- )
- output_file.write_text("\n".join(lines) + "\n", encoding="utf-8")
+ output_file.write_text(
+ "\n".join(self.merge_sections(lines, output_file)) + "\n",
+ encoding="utf-8",
+ )
else:
for section_id, section_lines in self.sections:
start_marker, end_marker = self.autogenerated_markers(section_id)
diff --git a/weblate/utils/tests/test_commands.py b/weblate/utils/tests/test_commands.py
index b390fa274082..716aaa68a7f5 100644
--- a/weblate/utils/tests/test_commands.py
+++ b/weblate/utils/tests/test_commands.py
@@ -10,10 +10,17 @@
from unittest.mock import patch
from django.core.management import call_command
+from django.core.management.base import CommandError
from django.test import SimpleTestCase, TestCase
from weblate.trans.tests.utils import TempDirMixin
from weblate.utils.commands import find_runtime_command, get_clean_env
+from weblate.utils.management.base import DocGeneratorCommand
+
+
+class DummyDocGeneratorCommand(DocGeneratorCommand):
+ def handle(self, *args, **options) -> None:
+ raise NotImplementedError
class CommandTests(SimpleTestCase, TempDirMixin):
@@ -23,6 +30,163 @@ def test_queues(self) -> None:
self.assertIn("celery:", output.getvalue())
+class DocGeneratorCommandTests(SimpleTestCase):
+ def build_block(self, command, section_id: str, content: list[str]) -> list[str]:
+ start_marker, end_marker = command.autogenerated_markers(section_id)
+ return command.insert_markers(content, start_marker, end_marker)
+
+ def test_write_sections_reorders_blocks_with_manual_content(self) -> None:
+ command = DummyDocGeneratorCommand()
+ command.sections = [
+ ("first", ["Fresh first section"]),
+ ("missing", ["Fresh missing section"]),
+ ("second", ["Fresh second section"]),
+ ]
+
+ existing_lines = [
+ "Preamble heading",
+ "================",
+ "",
+ *self.build_block(command, "second", ["Stale second section"]),
+ "",
+ "Second manual content.",
+ "",
+ *self.build_block(command, "first", ["Stale first section"]),
+ "",
+ "First manual content.",
+ "",
+ *self.build_block(command, "stale", ["Stale removed section"]),
+ "",
+ "Removed manual content.",
+ ]
+
+ with tempfile.NamedTemporaryFile(suffix=".rst", delete=False) as handle:
+ output_file = Path(handle.name)
+ self.addCleanup(output_file.unlink)
+ output_file.write_text("\n".join(existing_lines) + "\n", encoding="utf-8")
+
+ command.write_sections(output_file)
+
+ expected_lines = [
+ "Preamble heading",
+ "================",
+ "",
+ *self.build_block(command, "first", ["Fresh first section"]),
+ "",
+ "First manual content.",
+ "",
+ *self.build_block(command, "missing", ["Fresh missing section"]),
+ *self.build_block(command, "second", ["Fresh second section"]),
+ "",
+ "Second manual content.",
+ "",
+ ]
+ self.assertEqual(
+ output_file.read_text(encoding="utf-8"),
+ "\n".join(expected_lines) + "\n",
+ )
+
+ def test_write_sections_appends_after_manual_preamble(self) -> None:
+ command = DummyDocGeneratorCommand()
+ command.sections = [("first", ["Fresh first section"])]
+
+ with tempfile.NamedTemporaryFile(suffix=".rst", delete=False) as handle:
+ output_file = Path(handle.name)
+ self.addCleanup(output_file.unlink)
+ output_file.write_text(
+ "Manual preamble.\n\nStill manual.\n",
+ encoding="utf-8",
+ )
+
+ command.write_sections(output_file)
+
+ expected_lines = [
+ "Manual preamble.",
+ "",
+ "Still manual.",
+ *self.build_block(command, "first", ["Fresh first section"]),
+ ]
+ self.assertEqual(
+ output_file.read_text(encoding="utf-8"),
+ "\n".join(expected_lines) + "\n",
+ )
+
+ def test_write_sections_rejects_missing_end_marker(self) -> None:
+ command = DummyDocGeneratorCommand()
+ command.sections = [
+ ("first", ["Fresh first section"]),
+ ("second", ["Fresh second section"]),
+ ]
+
+ existing_lines = [
+ "Preamble heading",
+ "",
+ ".. AUTOGENERATED START: first",
+ "Stale first section",
+ "Manual content that should not be dropped.",
+ ".. AUTOGENERATED START: second",
+ ".. AUTOGENERATED END: second",
+ "Second manual content.",
+ ]
+
+ with tempfile.NamedTemporaryFile(suffix=".rst", delete=False) as handle:
+ output_file = Path(handle.name)
+ self.addCleanup(output_file.unlink)
+ original = "\n".join(existing_lines) + "\n"
+ output_file.write_text(original, encoding="utf-8")
+
+ with self.assertRaisesRegex(CommandError, "Missing autogenerated end marker"):
+ command.write_sections(output_file)
+
+ self.assertEqual(output_file.read_text(encoding="utf-8"), original)
+
+ def test_write_sections_tolerates_trailing_whitespace_in_markers(self) -> None:
+ command = DummyDocGeneratorCommand()
+ command.sections = [("first", ["Fresh first section"])]
+
+ start_marker, end_marker = command.autogenerated_markers("first")
+ existing_lines = [
+ "Preamble heading",
+ "",
+ f"{start_marker} ",
+ ".. This section is automatically generated by `./manage.py test`. Do not edit manually.",
+ "",
+ "Stale first section",
+ "",
+ f"{end_marker} ",
+ "",
+ "First manual content.",
+ ]
+
+ with tempfile.NamedTemporaryFile(suffix=".rst", delete=False) as handle:
+ output_file = Path(handle.name)
+ self.addCleanup(output_file.unlink)
+ output_file.write_text("\n".join(existing_lines) + "\n", encoding="utf-8")
+
+ command.write_sections(output_file)
+
+ expected_lines = [
+ "Preamble heading",
+ "",
+ *self.build_block(command, "first", ["Fresh first section"]),
+ "",
+ "First manual content.",
+ ]
+ self.assertEqual(
+ output_file.read_text(encoding="utf-8"),
+ "\n".join(expected_lines) + "\n",
+ )
+
+ def test_sections_are_not_shared_between_instances(self) -> None:
+ first_command = DummyDocGeneratorCommand()
+ second_command = DummyDocGeneratorCommand()
+
+ first_command.add_section("first", ["Fresh first section"])
+
+ self.assertEqual(first_command.sections, [("first", ["Fresh first section"])])
+ self.assertEqual(second_command.sections, [])
+
+
class DBCommandTests(TestCase):
def test_stats(self) -> None:
output = StringIO()