Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 64 additions & 4 deletions docs/admin/config.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1324,6 +1324,64 @@ Defaults to -1.
* :setting:`IP_BEHIND_REVERSE_PROXY`
* :setting:`IP_PROXY_HEADER`

.. setting:: LEGAL_DOCUMENT_CSS_CLASS

LEGAL_DOCUMENT_CSS_CLASS
------------------------

.. versionadded:: 2026.7

CSS class added to the wrappers around legal document templates.

Defaults to ``"tos"``, which enables the built-in legal document numbering and
spacing rules. Set this to an empty string to render legal documents without
the built-in numbering.

.. code-block:: python

LEGAL_DOCUMENT_CSS_CLASS = ""

.. seealso::

:ref:`legal`

.. setting:: LEGAL_HIDDEN_DOCUMENTS

LEGAL_HIDDEN_DOCUMENTS
----------------------

.. versionadded:: 2026.7

List of legal document page identifiers to hide from the legal module.

The ``index`` page is always visible. Supported document identifiers are
``terms``, ``cookies``, ``privacy``, and ``contracts``.

Hidden pages are removed from the legal menu and return a 404 response when
requested directly. Hiding ``terms`` or ``privacy`` is not recommended when
terms of service confirmation is enabled.

When ``terms`` or ``privacy`` is hidden, links exposed through the
``terms_url`` and ``privacy_url`` template variables use :setting:`LEGAL_URL`
and :setting:`PRIVACY_URL` as fallbacks when configured. If no fallback URL is
configured, the related link is omitted.

With terms of service confirmation enabled, hiding ``terms`` and setting
:setting:`LEGAL_URL` makes the confirmation page link to the external terms
document instead of embedding :file:`legal/documents/tos.html`.

In non-Docker deployments, define :setting:`LEGAL_HIDDEN_DOCUMENTS` and
:setting:`LEGAL_URL` before ``SPECTACULAR_SETTINGS`` is created so the API
schema terms link uses the same fallback.

.. code-block:: python

LEGAL_HIDDEN_DOCUMENTS = ("contracts",)

.. seealso::

:ref:`legal`

.. setting:: LEGAL_TOS_DATE

LEGAL_TOS_DATE
Expand Down Expand Up @@ -1353,8 +1411,9 @@ URL where your Weblate instance shows its legal documents.

.. hint::

Useful if you host your legal documents outside Weblate for embedding them inside Weblate.
Please check :ref:`legal` for details.
Useful if you host your legal documents outside Weblate instead of using
the :ref:`legal` module. When the legal module is enabled, Weblate links to
the internal legal pages by default.

Example:

Expand Down Expand Up @@ -1595,8 +1654,9 @@ URL where your Weblate instance shows its privacy policy.

.. hint::

Useful if you host your legal documents outside Weblate for embedding them inside Weblate,
please check :ref:`legal` for details.
Useful if you host your privacy policy outside Weblate instead of using
the :ref:`legal` module. When the legal module is enabled, Weblate links to
the internal legal pages by default.

Example:

Expand Down
33 changes: 33 additions & 0 deletions docs/admin/install/docker.rst
Original file line number Diff line number Diff line change
Expand Up @@ -908,6 +908,10 @@ Generic settings
:file:`/app/data/python/customize/templates/legal/documents`, see
:ref:`docker-static-override`.

Recreate the Docker container after changing this environment variable,
for example using :program:`docker compose up -d`. Restarting an existing
container does not apply changed environment values.

**Example:**

.. code-block:: yaml
Expand All @@ -920,6 +924,35 @@ Generic settings
* :ref:`legal`
* :ref:`docker-static-override`

.. envvar:: WEBLATE_LEGAL_DOCUMENT_CSS_CLASS

Configures :setting:`LEGAL_DOCUMENT_CSS_CLASS` in Docker deployments with
:envvar:`WEBLATE_LEGAL_INTEGRATION` enabled.

Set this to an empty string to disable the built-in legal document
numbering.

**Example:**

.. code-block:: yaml

environment:
WEBLATE_LEGAL_DOCUMENT_CSS_CLASS: ""

.. envvar:: WEBLATE_LEGAL_HIDDEN_DOCUMENTS

Configures :setting:`LEGAL_HIDDEN_DOCUMENTS` in Docker deployments with
:envvar:`WEBLATE_LEGAL_INTEGRATION` enabled.

Provide a comma-separated list of legal document page identifiers.

**Example:**

.. code-block:: yaml

environment:
WEBLATE_LEGAL_HIDDEN_DOCUMENTS: contracts

.. envvar:: WEBLATE_PUBLIC_ENGAGE

Enables :setting:`PUBLIC_ENGAGE`.
Expand Down
39 changes: 39 additions & 0 deletions docs/admin/optionals.rst
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,15 @@ following templates in the documents:
Privacy policy document
:file:`legal/documents/summary.html`
Short overview of the terms of service and privacy policy
:file:`legal/documents/contracts.html`
Subcontractor information

The legal module embeds these templates inside Weblate and uses
:file:`legal/documents/tos.html` for terms of service confirmation. This is
separate from :setting:`LEGAL_URL` and :setting:`PRIVACY_URL`, which are meant
for linking to externally hosted legal documents from the footer when the
legal module is not enabled. When the legal module is enabled, Weblate links to
the internal legal pages by default.

On changing the terms of service documents, please adjust
:setting:`LEGAL_TOS_DATE` so that users are forced to agree with the updated
Expand Down Expand Up @@ -193,11 +202,41 @@ Installation
:file:`/app/data/python/customize/templates/legal/documents`, see
:ref:`docker-static-override`.

Recreate the Docker container after changing environment variables, for
example using :program:`docker compose up -d`. Restarting an existing
container does not apply changed environment values.

Usage
+++++

After installation and editing, the legal documents are shown in the Weblate UI.

The legal document templates are regular Django templates. Text is translated
only when you use Django translation tags such as ``{% translate %}`` or
``{% blocktranslate %}``; plain HTML text is shown as written.

Legal pages and the sign-in and registration overview provide ``terms_url`` and
``privacy_url`` variables for linking to the terms of service and privacy
policy documents.

By default, legal document wrappers use the ``tos`` CSS class. This class
automatically numbers ``h2`` headings, paragraphs with ``item``, ``subitem``,
or ``subsubitem`` classes, and top-level ordered list items. If your legal
text already contains numbering, set :setting:`LEGAL_DOCUMENT_CSS_CLASS` to an
empty string to disable this styling.

Use :setting:`LEGAL_HIDDEN_DOCUMENTS` to hide optional legal pages such as
subcontractors from the legal menu. Hidden pages return a 404 response when
requested directly. If ``terms`` or ``privacy`` is hidden, links using
``terms_url`` or ``privacy_url`` fall back to :setting:`LEGAL_URL` or
:setting:`PRIVACY_URL` when configured, otherwise the link is omitted.

To use externally hosted legal documents with terms confirmation, configure
:setting:`LEGAL_HIDDEN_DOCUMENTS` to hide ``terms`` and ``privacy`` and set
:setting:`LEGAL_URL` and :setting:`PRIVACY_URL`. The confirmation page then
links to those external documents without requiring a
:file:`legal/documents/tos.html` template override.

.. _avatars:

Avatars
Expand Down
1 change: 1 addition & 0 deletions docs/changes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ Weblate 2026.7

* Management interface access control is now more fine-grained with dedicated site-wide permissions.
* Default commit and merge request message templates now use Conventional Commits, and settings forms can restore installation defaults for individual message templates.
* Documented :ref:`legal` customizations and added options to hide legal pages or disable document numbering.

.. rubric:: Bug fixes

Expand Down
30 changes: 27 additions & 3 deletions weblate/api/spectacular.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,14 @@

from __future__ import annotations

from typing import Any
from typing import TYPE_CHECKING, Any

from django.utils.functional import lazy
from django.utils.translation import gettext_lazy

if TYPE_CHECKING:
from collections.abc import Sequence


def get_doc_url_wrapper(page: str, anchor: str = "") -> str:
"""
Expand All @@ -23,8 +26,27 @@
return get_doc_url(page, anchor, doc_version="latest")


def get_legal_terms_url(
legal_hidden_documents: Sequence[str] | str = (), legal_url: str | None = None
) -> str | None:
if isinstance(legal_hidden_documents, str):
hidden_documents = legal_hidden_documents.split(",")
else:
hidden_documents = legal_hidden_documents

Check failure on line 35 in weblate/api/spectacular.py

View workflow job for this annotation

GitHub Actions / mypy

Incompatible types in assignment (expression has type "Sequence[str]", variable has type "list[str]")

for document in hidden_documents:
if document.strip() == "terms":
return legal_url
return "/legal/terms/"


def get_spectacular_settings(
installed_apps: list[str], site_url: str, site_title: str
installed_apps: list[str],
site_url: str,
site_title: str,
*,
legal_hidden_documents: Sequence[str] | str = (),
legal_url: str | None = None,
Comment thread
nijel marked this conversation as resolved.
) -> dict[str, Any]:
settings = {
# Use redoc from sidecar
Expand Down Expand Up @@ -202,7 +224,9 @@
"WEBHOOKS": ["weblate.addons.webhooks.change_event_webhook"],
}
if "weblate.legal" in installed_apps:
settings["TOS"] = "/legal/terms/"
terms_url = get_legal_terms_url(legal_hidden_documents, legal_url)
if terms_url:
settings["TOS"] = terms_url

return settings

Expand Down
4 changes: 4 additions & 0 deletions weblate/legal/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@
class WeblateLegalConf(AppConf):
# Current TOS date
LEGAL_TOS_DATE = DEFAULT_TOS_DATE
# CSS class for styling legal document templates
LEGAL_DOCUMENT_CSS_CLASS = "tos"
# Legal documents to hide from the UI
LEGAL_HIDDEN_DOCUMENTS = ()

class Meta:
prefix = ""
Expand Down
20 changes: 18 additions & 2 deletions weblate/legal/templates/legal/confirm.html
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,24 @@
<h4 class="card-title">{% translate "You have to agree to the General Terms and Conditions" %}</h4>
</div>
<div class="card-body">
<p>{% translate "Please read following General Terms and Conditions document:" %}</p>
<div class="list-group-item pre-scrollable tos">{% include "legal/documents/tos.html" %}</div>
{% if terms_document_hidden and terms_url %}
<p>{% translate "Please read the following legal documents:" %}</p>
<ul>
<li>
<a href="{{ terms_url }}">{% translate "General Terms and Conditions" %}</a>
</li>
{% if privacy_url %}
<li>
<a href="{{ privacy_url }}">{% translate "Privacy Policy" %}</a>
</li>
{% endif %}
</ul>
{% else %}
<p>{% translate "Please read the following General Terms and Conditions document:" %}</p>
<div class="list-group-item pre-scrollable{% if legal_document_css_class %} {{ legal_document_css_class }}{% endif %}">
{% include "legal/documents/tos.html" %}
</div>
{% endif %}
{{ form|crispy }}
{% csrf_token %}
</div>
Expand Down
4 changes: 3 additions & 1 deletion weblate/legal/templates/legal/contracts.html
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,9 @@
<div class="card-header">
<h4 class="card-title">{% translate "Subcontractors" %}</h4>
</div>
<div class="card-body tos">{% include "legal/documents/contracts.html" %}</div>
<div class="card-body{% if legal_document_css_class %} {{ legal_document_css_class }}{% endif %}">
{% include "legal/documents/contracts.html" %}
</div>
</div>

{% endblock content %}
30 changes: 18 additions & 12 deletions weblate/legal/templates/legal/cookies.html
Original file line number Diff line number Diff line change
Expand Up @@ -21,18 +21,24 @@ <h4 class="card-title">{% translate "Cookies Policy" %}</h4>
</div>
<div class="card-body">

<p>
{% blocktranslate %}This page is based on the General Terms and Conditions and the Privacy Policy, you should still read the original documents to fully understand them:{% endblocktranslate %}
</p>

<ul>
<li>
<a href="{% url 'legal:terms' %}">{% translate "General Terms and Conditions" %}</a>
</li>
<li>
<a href="{% url 'legal:privacy' %}">{% translate "Privacy Policy" %}</a>
</li>
</ul>
{% if terms_url or privacy_url %}
<p>
{% blocktranslate %}This page is based on the General Terms and Conditions and the Privacy Policy, you should still read the original documents to fully understand them:{% endblocktranslate %}
</p>

<ul>
{% if terms_url %}
<li>
<a href="{{ terms_url }}">{% translate "General Terms and Conditions" %}</a>
</li>
{% endif %}
{% if privacy_url %}
<li>
<a href="{{ privacy_url }}">{% translate "Privacy Policy" %}</a>
</li>
{% endif %}
</ul>
{% endif %}

<p>
{% blocktranslate %}Cookies are used on this site for the following:{% endblocktranslate %}
Expand Down
30 changes: 18 additions & 12 deletions weblate/legal/templates/legal/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -20,18 +20,24 @@ <h4 class="card-title">{% translate "Legal Terms Overview" %}</h4>

{% include 'legal/documents/summary.html' %}

<p>
{% blocktranslate %}This page is based on the General Terms and Conditions and the Privacy Policy, you should still read the original documents to fully understand them:{% endblocktranslate %}
</p>

<ul>
<li>
<a href="{% url 'legal:terms' %}">{% translate "General Terms and Conditions" %}</a>
</li>
<li>
<a href="{% url 'legal:privacy' %}">{% translate "Privacy Policy" %}</a>
</li>
</ul>
{% if terms_url or privacy_url %}
<p>
{% blocktranslate %}This page is based on the General Terms and Conditions and the Privacy Policy, you should still read the original documents to fully understand them:{% endblocktranslate %}
</p>

<ul>
{% if terms_url %}
<li>
<a href="{{ terms_url }}">{% translate "General Terms and Conditions" %}</a>
</li>
{% endif %}
{% if privacy_url %}
<li>
<a href="{{ privacy_url }}">{% translate "Privacy Policy" %}</a>
</li>
{% endif %}
</ul>
{% endif %}

</div>
</div>
Expand Down
4 changes: 3 additions & 1 deletion weblate/legal/templates/legal/privacy.html
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,9 @@
<div class="card-header">
<h4 class="card-title">{% translate "Privacy Policy" %}</h4>
</div>
<div class="card-body tos">{% include "legal/documents/privacy.html" %}</div>
<div class="card-body{% if legal_document_css_class %} {{ legal_document_css_class }}{% endif %}">
{% include "legal/documents/privacy.html" %}
</div>
</div>

{% endblock content %}
Loading
Loading