Skip to content

Commit 4ab2da8

Browse files
committed
feat(settings): improve commit messages
- Follow Conventional Commits specification and make the content of the commit messages consistent. - Add button to revert to the site defaults in case users wants to go back to the shipped/configured settings. Fixes #19806
1 parent d6f061b commit 4ab2da8

14 files changed

Lines changed: 251 additions & 50 deletions

File tree

docs/admin/config.rst

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -707,6 +707,12 @@ DEFAULT_ADD_MESSAGE, DEFAULT_ADDON_MESSAGE, DEFAULT_COMMIT_MESSAGE, DEFAULT_DELE
707707

708708
Default commit messages for different operations, please check :ref:`component` for details.
709709

710+
The built-in defaults follow Conventional Commits and include
711+
Weblate links where available. Changing these settings affects newly created
712+
defaults; existing message templates can be reset in the settings forms with
713+
:guilabel:`Restore site default`. For inherited values, restoring the site
714+
default also disables inheritance for that message.
715+
710716

711717
.. seealso::
712718

@@ -859,6 +865,9 @@ DEFAULT_PULL_MESSAGE
859865

860866
Configures the default title and message for pull requests.
861867

868+
The built-in default follows Conventional Commits and includes
869+
Weblate links and translation status.
870+
862871
.. setting:: ENABLE_AVATARS
863872

864873
ENABLE_AVATARS

docs/admin/projects.rst

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -416,6 +416,11 @@ The default values can be changed by :setting:`DEFAULT_ADD_MESSAGE`,
416416
:setting:`DEFAULT_DELETE_MESSAGE`, :setting:`DEFAULT_MERGE_MESSAGE`,
417417
:setting:`DEFAULT_PULL_MESSAGE`.
418418

419+
The built-in defaults follow Conventional Commits and include
420+
Weblate links where available. Use :guilabel:`Restore site default` next to a
421+
message editor to restore the current installation default for that message;
422+
for inherited values, this also disables inheritance for that message.
423+
419424
.. seealso::
420425

421426
* :ref:`workspace-inherited-settings`
@@ -1027,6 +1032,11 @@ The default value can be changed by :setting:`DEFAULT_ADD_MESSAGE`,
10271032
:setting:`DEFAULT_DELETE_MESSAGE`, :setting:`DEFAULT_MERGE_MESSAGE`,
10281033
:setting:`DEFAULT_PULL_MESSAGE`.
10291034

1035+
The built-in defaults follow Conventional Commits and include
1036+
Weblate links where available. Use :guilabel:`Restore site default` next to a
1037+
message editor to restore the current installation default for that message;
1038+
for inherited values, this also disables inheritance for that message.
1039+
10301040
.. seealso::
10311041

10321042
* :ref:`workspace-inherited-settings`

docs/admin/workspaces.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,10 @@ Default commit and merge request message templates for projects and components
150150
in this workspace. These templates use the same markup as component message
151151
settings.
152152

153+
The built-in defaults follow Conventional Commits and include
154+
Weblate links where available. Use :guilabel:`Restore site default` next to a
155+
message editor to restore the current installation default for that message.
156+
153157
.. seealso::
154158

155159
* :ref:`workspace-inherited-settings`

docs/changes.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ Weblate 2026.7
77

88
.. rubric:: Improvements
99

10+
* Default commit and merge request message templates now use Conventional Commits, and settings forms can restore installation defaults for individual message templates.
11+
1012
.. rubric:: Bug fixes
1113

1214
.. rubric:: Compatibility

weblate/static/loader-bootstrap.js

Lines changed: 46 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1152,6 +1152,52 @@ $(function () {
11521152
});
11531153
});
11541154

1155+
const getControlValue = (control) => {
1156+
if (control.tomselect) {
1157+
return control.tomselect.getValue();
1158+
}
1159+
return control.value;
1160+
};
1161+
const setControlValue = (control, value) => {
1162+
if (control.tomselect) {
1163+
control.tomselect.setValue(value, true);
1164+
} else {
1165+
control.value = value;
1166+
}
1167+
control.dispatchEvent(new Event("input", { bubbles: true }));
1168+
control.dispatchEvent(new Event("change", { bubbles: true }));
1169+
};
1170+
const setControlDisabled = (control, disabled) => {
1171+
control.disabled = disabled;
1172+
if (!control.tomselect) {
1173+
return;
1174+
}
1175+
if (disabled) {
1176+
control.tomselect.disable();
1177+
} else {
1178+
control.tomselect.enable();
1179+
}
1180+
};
1181+
1182+
document.querySelectorAll(".site-default-field-button").forEach((button) => {
1183+
const container = button.closest(".mb-3");
1184+
const control = container?.querySelector("[data-site-default-value]");
1185+
if (!control) {
1186+
return;
1187+
}
1188+
button.addEventListener("click", () => {
1189+
const inheritedSetting = button.closest("[data-inherited-setting]");
1190+
const inheritCheckbox = inheritedSetting?.querySelector(
1191+
".inherited-setting-toggle input[type=checkbox]",
1192+
);
1193+
if (inheritCheckbox?.checked) {
1194+
inheritCheckbox.checked = false;
1195+
inheritCheckbox.dispatchEvent(new Event("change", { bubbles: true }));
1196+
}
1197+
setControlValue(control, control.dataset.siteDefaultValue || "");
1198+
});
1199+
});
1200+
11551201
document.querySelectorAll("[data-inherited-setting]").forEach((el) => {
11561202
const checkbox = el.querySelector(
11571203
".inherited-setting-toggle input[type=checkbox]",
@@ -1168,31 +1214,6 @@ $(function () {
11681214
return;
11691215
}
11701216

1171-
const getControlValue = (control) => {
1172-
if (control.tomselect) {
1173-
return control.tomselect.getValue();
1174-
}
1175-
return control.value;
1176-
};
1177-
const setControlValue = (control, value) => {
1178-
if (control.tomselect) {
1179-
control.tomselect.setValue(value, true);
1180-
} else {
1181-
control.value = value;
1182-
}
1183-
};
1184-
const setControlDisabled = (control, disabled) => {
1185-
control.disabled = disabled;
1186-
if (!control.tomselect) {
1187-
return;
1188-
}
1189-
if (disabled) {
1190-
control.tomselect.disable();
1191-
} else {
1192-
control.tomselect.enable();
1193-
}
1194-
};
1195-
11961217
const updateInheritedSetting = (syncValue) => {
11971218
const disabled = checkbox.checked;
11981219
el.classList.toggle("is-inherited", disabled);

weblate/static/styles/main.css

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2019,6 +2019,23 @@ input.color_edit:checked + label {
20192019
margin-bottom: 0;
20202020
}
20212021

2022+
.form-label-actions {
2023+
display: flex;
2024+
flex-wrap: wrap;
2025+
align-items: center;
2026+
justify-content: space-between;
2027+
gap: 0.5rem;
2028+
margin-bottom: 0.5rem;
2029+
}
2030+
2031+
.form-label-actions .form-label {
2032+
margin-bottom: 0;
2033+
}
2034+
2035+
.form-label-actions .site-default-field-button {
2036+
flex: 0 0 auto;
2037+
}
2038+
20222039
.help-block {
20232040
color: #2a3744;
20242041
}

weblate/templates/bootstrap5/field.html

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
{% load crispy_forms_field translations %}
1+
{% load crispy_forms_field i18n icons translations %}
22

33
{% comment %}Based on crispy original, just adds form_field_doc_link.{% endcomment %}
44

@@ -15,11 +15,23 @@
1515
<{% if tag %}{{ tag }}{% else %}div{% endif %} id="div_{{ field.auto_id }}" class="{% if field|is_checkbox and form_show_labels %}form-check{% else %}mb-3{% if 'form-horizontal' in form_class %} row{% endif %}{% endif %}{% if wrapper_class %} {{ wrapper_class }}{% endif %}{% if field.css_classes %} {{ field.css_classes }}{% endif %}">
1616
{% if field.label and not field|is_checkbox and form_show_labels %}
1717
{% if field.use_fieldset %}<fieldset{% if 'form-horizontal' in form_class %} class="row"{% endif %}{% if field.aria_describedby %} aria-describedby="{{ field.aria_describedby }}"{% endif %}>{% endif %}
18-
<{% if field.use_fieldset %}legend{% else %}label{% endif %}
19-
{% if field.id_for_label %}for="{{ field.id_for_label }}"{% endif %} class="{% if 'form-horizontal' in form_class %}col-form-label pt-0{% else %}form-label{% endif %}{% if label_class %} {{ label_class }}{% endif %}{% if field.field.required %} requiredField{% endif %}{% if field.use_fieldset %} fs-6{% endif %}">
20-
{{ field.label }}{% if field.field.required %}<span class="asteriskField">*</span>{% endif %}
21-
{% form_field_doc_link field.form field %}
22-
</{% if field.use_fieldset %}legend{% else %}label{% endif %}>
18+
{% if field.field.site_default and not field.use_fieldset %}
19+
<div class="form-label-actions">
20+
<label {% if field.id_for_label %}for="{{ field.id_for_label }}"{% endif %} class="{% if 'form-horizontal' in form_class %}col-form-label pt-0{% else %}form-label{% endif %}{% if label_class %} {{ label_class }}{% endif %}{% if field.field.required %} requiredField{% endif %}">
21+
{{ field.label }}{% if field.field.required %}<span class="asteriskField">*</span>{% endif %}
22+
{% form_field_doc_link field.form field %}
23+
</label>
24+
<button type="button" class="btn btn-outline-secondary btn-xs site-default-field-button" data-site-default-target="{{ field.html_name }}"{% if field.id_for_label %} aria-controls="{{ field.id_for_label }}"{% endif %} title="{% translate "Restore site default" %}">
25+
{% icon "undo.svg" %} {% translate "Restore site default" %}
26+
</button>
27+
</div>
28+
{% else %}
29+
<{% if field.use_fieldset %}legend{% else %}label{% endif %}
30+
{% if field.id_for_label %}for="{{ field.id_for_label }}"{% endif %} class="{% if 'form-horizontal' in form_class %}col-form-label pt-0{% else %}form-label{% endif %}{% if label_class %} {{ label_class }}{% endif %}{% if field.field.required %} requiredField{% endif %}{% if field.use_fieldset %} fs-6{% endif %}">
31+
{{ field.label }}{% if field.field.required %}<span class="asteriskField">*</span>{% endif %}
32+
{% form_field_doc_link field.form field %}
33+
</{% if field.use_fieldset %}legend{% else %}label{% endif %}>
34+
{% endif %}
2335
{% endif %}
2436

2537
{% if field|is_checkboxselectmultiple or field|is_radioselect %}

weblate/trans/defaults.py

Lines changed: 28 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -57,38 +57,53 @@
5757
DEFAULT_VCS = "git"
5858

5959
DEFAULT_COMMIT_MESSAGE = (
60-
"Translated using Weblate ({{ language_name }})\n\n"
61-
"Currently translated at {{ stats.translated_percent }}% "
62-
"({{ stats.translated }} of {{ stats.all }} strings)\n\n"
60+
"chore(l10n): update {{ language_name }} translation\n\n"
6361
"Translation: {{ project_name }}/{{ component_name }}\n"
62+
"Language: {{ language_name }}\n"
63+
"Progress: {{ stats.translated_percent }}% "
64+
"({{ stats.translated }} of {{ stats.all }} strings)\n"
6465
"Translate-URL: {{ url }}"
6566
)
6667

67-
DEFAULT_ADD_MESSAGE = "Added translation using Weblate ({{ language_name }})\n\n"
68-
69-
DEFAULT_DELETE_MESSAGE = "Deleted translation using Weblate ({{ language_name }})\n\n"
68+
DEFAULT_ADD_MESSAGE = (
69+
"chore(l10n): add {{ language_name }} translation\n\n"
70+
"Translation: {{ project_name }}/{{ component_name }}\n"
71+
"Language: {{ language_name }}\n"
72+
"Translate-URL: {{ url }}"
73+
)
7074

71-
DEFAULT_MERGE_MESSAGE = "Merge branch '{{ component_remote_branch }}' into Weblate.\n\n"
75+
DEFAULT_DELETE_MESSAGE = (
76+
"chore(l10n): remove {{ language_name }} translation\n\n"
77+
"Translation: {{ project_name }}/{{ component_name }}\n"
78+
"Language: {{ language_name }}\n"
79+
"Translate-URL: {{ url }}"
80+
)
7281

73-
DEFAULT_ADDON_MESSAGE = """Update translation files
82+
DEFAULT_MERGE_MESSAGE = (
83+
"chore(l10n): merge remote changes\n\n"
84+
"Translation: {{ project_name }}/{{ component_name }}\n"
85+
"Remote-Branch: {{ component_remote_branch }}\n"
86+
"Translate-URL: {{ url }}"
87+
)
7488

75-
Updated by "{{ addon_name }}" add-on in Weblate.
89+
DEFAULT_ADDON_MESSAGE = """chore(l10n): update translation files
7690
91+
Add-on: {{ addon_name }}
7792
Translation: {{ project_name }}/{{ component_name }}
7893
Translate-URL: {{ url }}"""
7994

80-
DEFAULT_PULL_MESSAGE = """Translations update from {{ site_title }}
95+
DEFAULT_PULL_MESSAGE = """chore(l10n): update translations
8196
82-
Translations update from [{{ site_title }}]({{ site_url }}) for [{{ project_name }}/{{ component_name }}]({{url}}).
97+
Translations updated in [{{ site_title }}]({{ site_url }}) for [{{ project_name }}/{{ component_name }}]({{ url }}).
8398
8499
{% if component_linked_children %}
85-
It also includes following components:
100+
Included components:
86101
{% for linked in component_linked_children %}
87102
* [{{ linked.project_name }}/{{ linked.name }}]({{ linked.url }})
88103
{% endfor %}
89104
{% endif %}
90105
91-
Current translation status:
106+
Translation status:
92107
93108
![Weblate translation status]({{widget_url}})
94109
"""

weblate/trans/forms.py

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
from datetime import datetime
1212
from itertools import chain
1313
from secrets import token_hex
14-
from typing import TYPE_CHECKING, Any, ClassVar, Literal, cast
14+
from typing import TYPE_CHECKING, Any, ClassVar, Literal, Protocol, cast
1515

1616
import jsonschema
1717
from crispy_forms.bootstrap import InlineCheckboxes, InlineRadios, Tab, TabHolder
@@ -72,6 +72,7 @@
7272
)
7373
from weblate.trans.filter import FILTERS
7474
from weblate.trans.inherited_settings import (
75+
COMPONENT_MESSAGE_SETTINGS,
7576
INHERITABLE_COMPONENT_FLAGS,
7677
INHERITABLE_COMPONENT_SETTINGS,
7778
get_inherit_field_name,
@@ -151,6 +152,12 @@
151152
from weblate.trans.models.translation import NewUnitParams
152153
from weblate.utils.stats import CategoryLanguage, ProjectLanguage
153154

155+
156+
class SiteDefaultField(Protocol):
157+
site_default: bool
158+
widget: forms.Widget
159+
160+
154161
BUTTON_TEMPLATE = """
155162
<button type="button" class="btn btn-outline-primary {0}" title="{1}" {2}>{3}</button>
156163
"""
@@ -1821,6 +1828,7 @@ def setup_inherited_setting_values(self, field_name: str) -> None:
18211828
)
18221829

18231830
def setup_inherited_settings(self, parent_name: str, *, has_parent: bool) -> None:
1831+
setup_message_setting_site_defaults(self.fields)
18241832
self._inherited_setting_fields = set()
18251833
for field_name in INHERITABLE_COMPONENT_SETTINGS:
18261834
inherit_field = get_inherit_field_name(field_name)
@@ -1884,6 +1892,16 @@ def _post_clean(self) -> None:
18841892
self.restore_inherited_values()
18851893

18861894

1895+
def setup_message_setting_site_defaults(fields: dict[str, forms.Field]) -> None:
1896+
for field_name in COMPONENT_MESSAGE_SETTINGS:
1897+
if field_name not in fields:
1898+
continue
1899+
setting_name = f"DEFAULT_{field_name.upper()}"
1900+
field = cast("SiteDefaultField", fields[field_name])
1901+
field.site_default = True
1902+
field.widget.attrs["data-site-default-value"] = getattr(settings, setting_name)
1903+
1904+
18871905
class SelectChecksWidget(SortedSelectMultiple):
18881906
def __init__(self, attrs=None, choices=()) -> None:
18891907
choices = CHECKS.get_choices()

weblate/trans/tests/test_file_format_params.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -364,7 +364,7 @@ def _test_closing_tags(self, closing_tags_active: bool = True) -> None:
364364
new_revision = self.component.repository.last_revision
365365
self.assertNotEqual(rev, new_revision)
366366
new_commit = self.component.repository.show(new_revision)
367-
self.assertIn("Added translation using Weblate (German)", new_commit)
367+
self.assertIn("chore(l10n): add German translation", new_commit)
368368
if closing_tags_active:
369369
self.assertIn(
370370
'<location filename="main.c" line="11"></location>', new_commit
@@ -552,7 +552,7 @@ def test_last_translator_header(self):
552552
rev3 = self.component.repository.last_revision
553553
commit3 = self.component.repository.show(rev3)
554554
self.assertNotEqual(rev2, rev3)
555-
self.assertIn("Added translation using Weblate (Polish)", commit3)
555+
self.assertIn("chore(l10n): add Polish translation", commit3)
556556
self.assertNotIn("Last-Translator: Automatically generated", commit3)
557557

558558
# check header is present when a new language is added and parameter set to True
@@ -561,7 +561,7 @@ def test_last_translator_header(self):
561561
rev4 = self.component.repository.last_revision
562562
self.assertNotEqual(rev3, rev4)
563563
commit4 = self.component.repository.show(rev4)
564-
self.assertIn("Added translation using Weblate (French)", commit4)
564+
self.assertIn("chore(l10n): add French translation", commit4)
565565
self.assertIn("Last-Translator: Automatically generated", commit4)
566566

567567
def test_x_generator_header(self):

0 commit comments

Comments
 (0)