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()