diff --git a/docs/admin/backup.rst b/docs/admin/backup.rst index 955719237f32..23f099db5fde 100644 --- a/docs/admin/backup.rst +++ b/docs/admin/backup.rst @@ -32,6 +32,14 @@ The generated backups are kept on the server as configured by :setting:`PROJECT_BACKUP_KEEP_DAYS` and :setting:`PROJECT_BACKUP_KEEP_COUNT` (it defaults to keep at most 3 backups for 30 days). +.. note:: + + Restoring of the backup might fail if the restoring server has different set + of :ref:`languages` or different configuration of + :setting:`SIMPLIFY_LANGUAGES`. The restore will tell you which language + codes could not be processed and you can then add missing langage + definitions manually. + Automated backup using BorgBackup --------------------------------- diff --git a/weblate/trans/backups.py b/weblate/trans/backups.py index c1d67835969b..65702c30691f 100644 --- a/weblate/trans/backups.py +++ b/weblate/trans/backups.py @@ -8,6 +8,7 @@ import json import os +from collections import defaultdict from datetime import datetime from itertools import chain from shutil import copyfileobj @@ -329,6 +330,18 @@ def load_components(self, zipfile, callback: Callable | None = None): raise ValueError( f'Component {data["component"]["name"]} uses unsupported VCS: {data["component"]["vcs"]}' ) + # Validate translations have unique languages + languages = defaultdict(list) + for item in data["translations"]: + language = self.import_language(item["language_code"]) + languages[language.code].append(item["language_code"]) + + for code, values in languages.items(): + if len(values) > 1: + raise ValueError( + f"Several languages from backup map to single language on this server {values} -> {code}" + ) + if callback is not None: callback(zipfile, data) diff --git a/weblate/trans/tests/data/projectbackup-duplicate.zip b/weblate/trans/tests/data/projectbackup-duplicate.zip new file mode 100644 index 000000000000..48ca21544d4a Binary files /dev/null and b/weblate/trans/tests/data/projectbackup-duplicate.zip differ diff --git a/weblate/trans/tests/test_backups.py b/weblate/trans/tests/test_backups.py index 7c3910dad36f..5df032202366 100644 --- a/weblate/trans/tests/test_backups.py +++ b/weblate/trans/tests/test_backups.py @@ -23,6 +23,7 @@ TEST_SCREENSHOT = get_test_file("screenshot.png") TEST_BACKUP = get_test_file("projectbackup-4.14.zip") +TEST_BACKUP_DUPLICATE = get_test_file("projectbackup-duplicate.zip") class BackupsTest(ViewTestCase): @@ -154,6 +155,13 @@ def test_restore_4_14(self): set(restored.label_set.values_list("name", "color")), ) + def test_restore_duplicate(self): + if not connection.features.can_return_rows_from_bulk_insert: + raise SkipTest("Not supported") + restore = ProjectBackup(TEST_BACKUP_DUPLICATE) + with self.assertRaises(ValueError): + restore.validate() + def test_cleanup(self): cleanup_project_backups() self.assertLessEqual(len(self.project.list_backups()), 3)