feat(file_format_params): Gettext PO header configuration with file format parameters#18982
feat(file_format_params): Gettext PO header configuration with file format parameters#18982gersona wants to merge 10 commits intoWeblateOrg:mainfrom
Conversation
gersona
commented
Apr 13, 2026
- Fixes Migrate gettext specific settings to file format parameters #16971
- Add file format params for .po file headers
- migrate set_language_team attribute to File format params
- documentation update
- update backup schema
|
Should the gettext file format parameters also apply to the headers set in the PO exporters? For example, |
❌ 16 Tests Failed:
View the top 3 failed test(s) by shortest run time
To view more test analytics, go to the Test Analytics Dashboard |
|
Yes, exporters honor params since #18830 |
9d08c23 to
bc121b2
Compare
There was a problem hiding this comment.
Pull request overview
Adds Gettext PO/POT header configuration via component-level file format parameters, migrating away from the legacy project-level set_language_team setting, and updates related backup/schema/docs/test coverage.
Changes:
- Introduces new
po_*file format parameters to control PO/POT header updates (Language-Team, Last-Translator, X-Generator, Report-Msgid-Bugs-To). - Migrates existing
Project.set_language_teaminto componentfile_format_paramsand removes the project field + associated UI/API/docs references. - Updates backup restore/migration CI scripts and expands tests to validate the new behavior.
Reviewed changes
Copilot reviewed 29 out of 29 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
weblate/trans/tests/test_file_format_params.py |
Adds tests verifying PO header update behavior under the new file format params. |
weblate/trans/tests/test_backups.py |
Verifies backup restore migrates legacy set_language_team into component file format params. |
weblate/trans/models/translation.py |
Uses new gettext file format params to conditionally update PO headers and passes params into update_header. |
weblate/trans/models/project.py |
Removes legacy set_language_team project field. |
weblate/trans/migrations/0073_migrate_gettext_header_settings.py |
Data migration intended to copy legacy project setting into component file_format_params and set defaults. |
weblate/trans/migrations/0074_remove_project_set_language_team.py |
Removes the set_language_team DB field from Project. |
weblate/trans/forms.py |
Removes set_language_team from project forms/tabs. |
weblate/trans/fixtures/simple-project.json |
Drops set_language_team from fixture data. |
weblate/trans/file_format_params.py |
Adds po_* params definitions + registration for gettext header controls. |
weblate/trans/backups.py |
Attempts to migrate legacy set_language_team during restore into component file_format_params. |
weblate/formats/txt.py |
Adjusts type hint for create_new_file language argument to Language. |
weblate/formats/ttkit.py |
Threads file_format_params through untranslate_store/update_header and conditions header fields on the new params. |
weblate/formats/tests/test_exporters.py |
Adds exporter test coverage for toggling Language-Team and X-Generator headers. |
weblate/formats/external.py |
Passes file_format_params to untranslate_store during new file creation (but has a signature/type mismatch). |
weblate/formats/exporters.py |
Makes PO exporter header generation conditional on new params. |
weblate/formats/convert.py |
Adjusts type hint for create_new_file language argument to Language. |
weblate/formats/base.py |
Updates update_header signature and plumbs component file_format_params into format setup. |
weblate/api/serializers.py |
Removes set_language_team from Project API serializer fields. |
weblate/addons/tests.py |
Extends gettext addon tests to cover disabling Report-Msgid-Bugs-To header updates. |
weblate/addons/gettext.py |
Conditions Report-Msgid-Bugs-To updates on the new file format param and passes params to update_header. |
docs/specs/schemas/weblate-backup.schema.json |
Removes legacy set_language_team from backup schema required fields. |
docs/snippets/file-format-parameters.rst |
Documents the new gettext po_* file format parameters. |
docs/formats/gettext.rst |
Updates Gettext docs to reference file format params instead of the legacy project setting. |
docs/changes.rst |
Release notes for new file format params and deprecation/removal of set_language_team. |
docs/api.rst |
Removes set_language_team from documented project API response. |
docs/admin/projects.rst |
Removes the admin documentation section for the old project-level setting. |
ci/run-migrate |
Adds CI migration test setup/assert steps for gettext header settings migration. |
ci/migrate-scripts/setup-gettext-header-settings.py |
Adds migration test setup data for legacy set_language_team -> component params migration. |
ci/migrate-scripts/assert-gettext-header-settings.py |
Asserts migrated component file_format_params contain the expected new keys/values. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| project=project1, | ||
| ), | ||
| Component( | ||
| name="gettext-header-settings-component-2", | ||
| slug="gettext-header-settings-component-2", | ||
| project=project2, |
There was a problem hiding this comment.
The created Components don’t specify file_format. For components created with the default empty string, the migration under test (0073) filters on file_format__in=("po", "po-mono"), so these rows won’t be migrated and the corresponding assert script will fail.
Set file_format="po" (and any other minimal required fields) on these Components so the migration test setup actually exercises the gettext header settings migration.
| project=project1, | |
| ), | |
| Component( | |
| name="gettext-header-settings-component-2", | |
| slug="gettext-header-settings-component-2", | |
| project=project2, | |
| project=project1, | |
| file_format="po", | |
| ), | |
| Component( | |
| name="gettext-header-settings-component-2", | |
| slug="gettext-header-settings-component-2", | |
| project=project2, | |
| file_format="po", |
| file_format_params = component.file_format_params or {} | ||
| file_format_params["po_set_language_team"] = component.project.set_language_team | ||
| file_format_params["po_set_last_translator"] = True | ||
| file_format_params["po_set_x_generator"] = True | ||
| file_format_params["po_report_msgid_bugs_to"] = True |
There was a problem hiding this comment.
In this data migration, file_format_params = component.file_format_params or {} will create a new dict when file_format_params is an empty {} (which is the default), and the updated dict is never assigned back to component.file_format_params. As a result, bulk_update() will persist the original (empty) JSON and the migration won’t actually set any of the new po_* keys for many components.
Fix by always copying/mutating and then assigning back, e.g. copy the existing dict (even if empty), set keys on the copy, and set component.file_format_params = copied_dict before adding to to_update.
| file_format_params = component.file_format_params or {} | |
| file_format_params["po_set_language_team"] = component.project.set_language_team | |
| file_format_params["po_set_last_translator"] = True | |
| file_format_params["po_set_x_generator"] = True | |
| file_format_params["po_report_msgid_bugs_to"] = True | |
| file_format_params = dict(component.file_format_params or {}) | |
| file_format_params["po_set_language_team"] = component.project.set_language_team | |
| file_format_params["po_set_last_translator"] = True | |
| file_format_params["po_set_x_generator"] = True | |
| file_format_params["po_report_msgid_bugs_to"] = True | |
| component.file_format_params = file_format_params |
| if component.file_format in {"po", "po-mono"}: | ||
| component.file_format_params["po_set_language_team"] = ( | ||
| self.set_language_team | ||
| ) |
There was a problem hiding this comment.
self.set_language_team defaults to False when the backup doesn’t contain the legacy set_language_team project field, but this block still unconditionally writes po_set_language_team=False for every restored PO component. That can clobber the actual component-level value stored in component.file_format_params in backups created by newer versions, and it also flips the effective default from True to False.
Consider tracking whether the legacy key was present (e.g., store None when absent) and only setting po_set_language_team when restoring an old backup and the component doesn’t already have po_set_language_team set.
| kwargs = self.data["project"].copy() | ||
| kwargs["name"] = project_name | ||
| kwargs["slug"] = project_slug | ||
| self.set_language_team = kwargs.pop("set_language_team", False) | ||
| self.project = project = Project.objects.create(**kwargs) |
There was a problem hiding this comment.
Using kwargs.pop("set_language_team", False) loses the ability to distinguish “old backup didn’t have this field” vs “old backup explicitly had it set to False”. This makes it hard to avoid overwriting component po_set_language_team values when restoring backups created after the migration.
Consider popping with a sentinel/None (and updating the type of self.set_language_team accordingly) so you can conditionally apply the migration only when the legacy field is actually present.
| def create_new_file( | ||
| cls, | ||
| filename: str, | ||
| language: str, | ||
| base: str, | ||
| callback: Callable | None = None, | ||
| file_format_params: FileFormatParams | None = None, # noqa: ARG003 | ||
| file_format_params: FileFormatParams | None = None, | ||
| ) -> None: | ||
| """Handle creation of new translation file.""" | ||
| if not base: | ||
| msg = "Not supported" | ||
| raise ValueError(msg) | ||
| # Parse file | ||
| store = cls(base) | ||
| if callback: | ||
| callback(store) | ||
| store.untranslate_store(language) | ||
| store.untranslate_store(language, file_format_params=file_format_params) | ||
| with open(filename, "wb") as handle: |
There was a problem hiding this comment.
XlsxFormat.create_new_file() still accepts language: str and passes that through to store.untranslate_store(...). But XlsxFormat inherits from TTKitFormat via CSVFormat, where untranslate_store() now expects a Language model and accesses language.code. Passing a string here will raise AttributeError when adding a new language for XLSX.
Update this signature to language: Language (matching TranslationFormat.create_new_file) and pass the Language instance through; also consider constructing store = cls(base, file_format_params=file_format_params) for consistency with TTKitFormat.create_new_file.