diff --git a/.stylelintrc.yaml b/.stylelintrc.yaml index ff5dd33a8..f5cc1d3ce 100644 --- a/.stylelintrc.yaml +++ b/.stylelintrc.yaml @@ -3,7 +3,7 @@ extends: - stylelint-config-standard-scss plugins: - stylelint-no-unsupported-browser-features - - stylelint-scss rules: import-notation: string no-descending-specificity: ~ + scss/at-extend-no-missing-placeholder: null diff --git a/betty/assets/locale/ar/betty.po b/betty/assets/locale/ar/betty.po index 9a3d000de..ae4ed9268 100644 --- a/betty/assets/locale/ar/betty.po +++ b/betty/assets/locale/ar/betty.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: Betty VERSION\n" "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" -"POT-Creation-Date: 2024-12-07 15:18+0000\n" +"POT-Creation-Date: 2024-12-16 21:21+0000\n" "PO-Revision-Date: 2024-11-30 19:00+0000\n" "Last-Translator: Bart Feenstra \n" "Language: ar\n" @@ -256,6 +256,9 @@ msgstr "" msgid "Clear all caches" msgstr "" +msgid "Close" +msgstr "" + msgid "Conference" msgstr "" @@ -283,7 +286,7 @@ msgstr "" msgid "Correspondence" msgstr "" -msgid "Cotton Candy is Betty's default theme." +msgid "Cotton Candy is Betty's legacy theme." msgstr "" msgid "Could not extract {file_path} as a gzip file (*.gz)." @@ -485,6 +488,9 @@ msgstr "" msgid "Generating your site to {output_directory}." msgstr "" +msgid "Go to the front page" +msgstr "" + msgid "HTTP API Documentation" msgstr "" @@ -506,6 +512,9 @@ msgstr "" msgid "I'm sorry, dear, but it seems there are no sources." msgstr "" +msgid "I'm sorry, dear, but it seems there is nothing to show." +msgstr "" + msgid "I'm sorry, dear, but it seems this page does not exist." msgstr "" @@ -527,6 +536,9 @@ msgstr "" msgid "Invalid YAML: {error}." msgstr "" +msgid "Keywords" +msgstr "" + msgid "Language" msgstr "" @@ -600,6 +612,9 @@ msgstr "زواج" msgid "Media" msgstr "" +msgid "Media references" +msgstr "" + msgid "Menu" msgstr "" @@ -746,6 +761,9 @@ msgstr "" msgid "Saved your project to {configuration_file}." msgstr "" +msgid "Search" +msgstr "" + msgid "Serve a generated site" msgstr "" @@ -897,9 +915,21 @@ msgstr "" msgid "Timeline" msgstr "الخط الزمني" +msgid "Toggle primary navigation" +msgstr "" + +msgid "Toggle search" +msgstr "" + +msgid "Toggle translations" +msgstr "" + msgid "Town" msgstr "" +msgid "Translations" +msgstr "" + msgid "Trees" msgstr "" @@ -994,7 +1024,7 @@ msgstr "" msgid "" "Your project has no theme enabled. This means your site's pages may look " -"bare. Try the \"cotton-candy\" extension." +"bare. Try the \"raspberry-mint\" extension." msgstr "" msgid "around {date}" diff --git a/betty/assets/locale/betty.pot b/betty/assets/locale/betty.pot index ee310d15d..f7b6c53d4 100644 --- a/betty/assets/locale/betty.pot +++ b/betty/assets/locale/betty.pot @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: Betty VERSION\n" "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" -"POT-Creation-Date: 2024-12-07 15:18+0000\n" +"POT-Creation-Date: 2024-12-16 21:21+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -244,6 +244,9 @@ msgstr "" msgid "Clear all caches" msgstr "" +msgid "Close" +msgstr "" + msgid "Conference" msgstr "" @@ -271,7 +274,7 @@ msgstr "" msgid "Correspondence" msgstr "" -msgid "Cotton Candy is Betty's default theme." +msgid "Cotton Candy is Betty's legacy theme." msgstr "" msgid "Could not extract {file_path} as a gzip file (*.gz)." @@ -473,6 +476,9 @@ msgstr "" msgid "Generating your site to {output_directory}." msgstr "" +msgid "Go to the front page" +msgstr "" + msgid "HTTP API Documentation" msgstr "" @@ -494,6 +500,9 @@ msgstr "" msgid "I'm sorry, dear, but it seems there are no sources." msgstr "" +msgid "I'm sorry, dear, but it seems there is nothing to show." +msgstr "" + msgid "I'm sorry, dear, but it seems this page does not exist." msgstr "" @@ -515,6 +524,9 @@ msgstr "" msgid "Invalid YAML: {error}." msgstr "" +msgid "Keywords" +msgstr "" + msgid "Language" msgstr "" @@ -588,6 +600,9 @@ msgstr "" msgid "Media" msgstr "" +msgid "Media references" +msgstr "" + msgid "Menu" msgstr "" @@ -734,6 +749,9 @@ msgstr "" msgid "Saved your project to {configuration_file}." msgstr "" +msgid "Search" +msgstr "" + msgid "Serve a generated site" msgstr "" @@ -873,9 +891,21 @@ msgstr "" msgid "Timeline" msgstr "" +msgid "Toggle primary navigation" +msgstr "" + +msgid "Toggle search" +msgstr "" + +msgid "Toggle translations" +msgstr "" + msgid "Town" msgstr "" +msgid "Translations" +msgstr "" + msgid "Trees" msgstr "" @@ -970,7 +1000,7 @@ msgstr "" msgid "" "Your project has no theme enabled. This means your site's pages may look " -"bare. Try the \"cotton-candy\" extension." +"bare. Try the \"raspberry-mint\" extension." msgstr "" msgid "around {date}" diff --git a/betty/assets/locale/de-DE/betty.po b/betty/assets/locale/de-DE/betty.po index 49c541372..cecafb3ca 100644 --- a/betty/assets/locale/de-DE/betty.po +++ b/betty/assets/locale/de-DE/betty.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: Betty VERSION\n" "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" -"POT-Creation-Date: 2024-12-07 15:18+0000\n" +"POT-Creation-Date: 2024-12-16 21:21+0000\n" "PO-Revision-Date: 2024-11-29 18:58+0000\n" "Last-Translator: Ettore Atalan \n" "Language: de_DE\n" @@ -273,6 +273,9 @@ msgstr "" msgid "Clear all caches" msgstr "" +msgid "Close" +msgstr "" + msgid "Conference" msgstr "Konferenz" @@ -300,8 +303,8 @@ msgstr "" msgid "Correspondence" msgstr "Schriftverkehr" -msgid "Cotton Candy is Betty's default theme." -msgstr "Cotton Candy ist Betty's Standard-Thema." +msgid "Cotton Candy is Betty's legacy theme." +msgstr "" msgid "Could not extract {file_path} as a gzip file (*.gz)." msgstr "Konnte {file_path} nicht als gzip (*.gz) Date extrahieren." @@ -516,6 +519,9 @@ msgstr "Erzeuge statische, öffentliche Dateien..." msgid "Generating your site to {output_directory}." msgstr "Erzeuge deine Site nach {output_directory}." +msgid "Go to the front page" +msgstr "" + msgid "HTTP API Documentation" msgstr "HTTP API Dokumentation" @@ -537,6 +543,9 @@ msgstr "Es tut mir leid, aber sieht so aus, als ob es keine Orte gibt." msgid "I'm sorry, dear, but it seems there are no sources." msgstr "Es tut mir leid, aber sieht so aus, als ob es keine Quellen gibt." +msgid "I'm sorry, dear, but it seems there is nothing to show." +msgstr "" + msgid "I'm sorry, dear, but it seems this page does not exist." msgstr "" @@ -558,6 +567,9 @@ msgstr "Invalid JSON: {error}." msgid "Invalid YAML: {error}." msgstr "Invalid YAML: {error}." +msgid "Keywords" +msgstr "" + msgid "Language" msgstr "Sprache" @@ -631,6 +643,9 @@ msgstr "Hochzeit" msgid "Media" msgstr "Medien" +msgid "Media references" +msgstr "" + msgid "Menu" msgstr "Menü" @@ -784,6 +799,9 @@ msgstr "Ruhestand" msgid "Saved your project to {configuration_file}." msgstr "" +msgid "Search" +msgstr "" + msgid "Serve a generated site" msgstr "" @@ -934,9 +952,21 @@ msgstr "" msgid "Timeline" msgstr "Zeitschiene" +msgid "Toggle primary navigation" +msgstr "" + +msgid "Toggle search" +msgstr "" + +msgid "Toggle translations" +msgstr "" + msgid "Town" msgstr "" +msgid "Translations" +msgstr "" + msgid "Trees" msgstr "Bäume" @@ -1040,7 +1070,7 @@ msgstr "" msgid "" "Your project has no theme enabled. This means your site's pages may look " -"bare. Try the \"cotton-candy\" extension." +"bare. Try the \"raspberry-mint\" extension." msgstr "" msgid "around {date}" diff --git a/betty/assets/locale/fr-FR/betty.po b/betty/assets/locale/fr-FR/betty.po index cc37ffb0b..81bfb1d15 100644 --- a/betty/assets/locale/fr-FR/betty.po +++ b/betty/assets/locale/fr-FR/betty.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: PROJECT VERSION\n" "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" -"POT-Creation-Date: 2024-12-07 15:18+0000\n" +"POT-Creation-Date: 2024-12-16 21:21+0000\n" "PO-Revision-Date: 2024-11-29 18:58+0000\n" "Last-Translator: Bart Feenstra \n" "Language: fr_FR\n" @@ -251,6 +251,9 @@ msgstr "" msgid "Clear all caches" msgstr "" +msgid "Close" +msgstr "" + msgid "Conference" msgstr "" @@ -278,7 +281,7 @@ msgstr "" msgid "Correspondence" msgstr "Correspondance" -msgid "Cotton Candy is Betty's default theme." +msgid "Cotton Candy is Betty's legacy theme." msgstr "" msgid "Could not extract {file_path} as a gzip file (*.gz)." @@ -480,6 +483,9 @@ msgstr "" msgid "Generating your site to {output_directory}." msgstr "" +msgid "Go to the front page" +msgstr "" + msgid "HTTP API Documentation" msgstr "" @@ -501,6 +507,9 @@ msgstr "" msgid "I'm sorry, dear, but it seems there are no sources." msgstr "" +msgid "I'm sorry, dear, but it seems there is nothing to show." +msgstr "" + msgid "I'm sorry, dear, but it seems this page does not exist." msgstr "" @@ -522,6 +531,9 @@ msgstr "" msgid "Invalid YAML: {error}." msgstr "" +msgid "Keywords" +msgstr "" + msgid "Language" msgstr "Langage" @@ -595,6 +607,9 @@ msgstr "Mariage" msgid "Media" msgstr "Média" +msgid "Media references" +msgstr "" + msgid "Menu" msgstr "Menu" @@ -741,6 +756,9 @@ msgstr "Retraite" msgid "Saved your project to {configuration_file}." msgstr "" +msgid "Search" +msgstr "" + msgid "Serve a generated site" msgstr "" @@ -884,9 +902,21 @@ msgstr "" msgid "Timeline" msgstr "Chronologie" +msgid "Toggle primary navigation" +msgstr "" + +msgid "Toggle search" +msgstr "" + +msgid "Toggle translations" +msgstr "" + msgid "Town" msgstr "" +msgid "Translations" +msgstr "" + msgid "Trees" msgstr "" @@ -983,7 +1013,7 @@ msgstr "" msgid "" "Your project has no theme enabled. This means your site's pages may look " -"bare. Try the \"cotton-candy\" extension." +"bare. Try the \"raspberry-mint\" extension." msgstr "" msgid "around {date}" diff --git a/betty/assets/locale/he/betty.po b/betty/assets/locale/he/betty.po index dd350608d..d915f82f3 100644 --- a/betty/assets/locale/he/betty.po +++ b/betty/assets/locale/he/betty.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: Betty VERSION\n" "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" -"POT-Creation-Date: 2024-12-07 15:18+0000\n" +"POT-Creation-Date: 2024-12-16 21:21+0000\n" "PO-Revision-Date: 2024-11-30 19:00+0000\n" "Last-Translator: Bart Feenstra \n" "Language: he\n" @@ -247,6 +247,9 @@ msgstr "" msgid "Clear all caches" msgstr "" +msgid "Close" +msgstr "" + msgid "Conference" msgstr "" @@ -274,7 +277,7 @@ msgstr "" msgid "Correspondence" msgstr "" -msgid "Cotton Candy is Betty's default theme." +msgid "Cotton Candy is Betty's legacy theme." msgstr "" msgid "Could not extract {file_path} as a gzip file (*.gz)." @@ -476,6 +479,9 @@ msgstr "" msgid "Generating your site to {output_directory}." msgstr "" +msgid "Go to the front page" +msgstr "" + msgid "HTTP API Documentation" msgstr "" @@ -497,6 +503,9 @@ msgstr "" msgid "I'm sorry, dear, but it seems there are no sources." msgstr "" +msgid "I'm sorry, dear, but it seems there is nothing to show." +msgstr "" + msgid "I'm sorry, dear, but it seems this page does not exist." msgstr "" @@ -518,6 +527,9 @@ msgstr "" msgid "Invalid YAML: {error}." msgstr "" +msgid "Keywords" +msgstr "" + msgid "Language" msgstr "" @@ -591,6 +603,9 @@ msgstr "" msgid "Media" msgstr "" +msgid "Media references" +msgstr "" + msgid "Menu" msgstr "" @@ -737,6 +752,9 @@ msgstr "" msgid "Saved your project to {configuration_file}." msgstr "" +msgid "Search" +msgstr "" + msgid "Serve a generated site" msgstr "" @@ -876,9 +894,21 @@ msgstr "" msgid "Timeline" msgstr "" +msgid "Toggle primary navigation" +msgstr "" + +msgid "Toggle search" +msgstr "" + +msgid "Toggle translations" +msgstr "" + msgid "Town" msgstr "" +msgid "Translations" +msgstr "" + msgid "Trees" msgstr "" @@ -973,7 +1003,7 @@ msgstr "" msgid "" "Your project has no theme enabled. This means your site's pages may look " -"bare. Try the \"cotton-candy\" extension." +"bare. Try the \"raspberry-mint\" extension." msgstr "" msgid "around {date}" diff --git a/betty/assets/locale/nl-NL/betty.po b/betty/assets/locale/nl-NL/betty.po index a522cdd1a..d4fa9fc61 100644 --- a/betty/assets/locale/nl-NL/betty.po +++ b/betty/assets/locale/nl-NL/betty.po @@ -7,16 +7,16 @@ msgid "" msgstr "" "Project-Id-Version: PROJECT VERSION\n" "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" -"POT-Creation-Date: 2024-12-07 15:18+0000\n" +"POT-Creation-Date: 2024-12-16 21:21+0000\n" "PO-Revision-Date: 2024-12-08 20:00+0000\n" "Last-Translator: Bart Feenstra \n" -"Language-Team: Dutch \n" -"Language: nl-NL\n" +"Language: nl_NL\n" +"Language-Team: Dutch " +"\n" +"Plural-Forms: nplurals=2; plural=n != 1;\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" -"Plural-Forms: nplurals=2; plural=n != 1;\n" -"X-Generator: Weblate 5.9-dev\n" "Generated-By: Babel 2.16.0\n" msgid "\"{hex_value}\" is not a valid hexadecimal color, such as #ffc0cb." @@ -155,10 +155,10 @@ msgid "" "add to your site, such as media galleries, maps, and browsable family " "trees." msgstr "" -"Betty is een programma dat van een stamboom een website bouwt zoals de site " -"die je nu aan het bezoeken bent. Des te meer informatie je genealogisch " -"onderzoek bevat, des te meer interactiviteit Betty aan je site toe kan " -"voegen, zoals mediagalerijen, kaarten, en visuele stambomen." +"Betty is een programma dat van een stamboom een website bouwt zoals de " +"site die je nu aan het bezoeken bent. Des te meer informatie je " +"genealogisch onderzoek bevat, des te meer interactiviteit Betty aan je " +"site toe kan voegen, zoals mediagalerijen, kaarten, en visuele stambomen." msgid "" "Betty is unfamiliar with Gramps event \"{event_id}\"'s type of " @@ -291,6 +291,9 @@ msgstr "Stad" msgid "Clear all caches" msgstr "Leeg alle caches" +msgid "Close" +msgstr "Sluiten" + msgid "Conference" msgstr "Conferentie" @@ -320,8 +323,8 @@ msgstr "" msgid "Correspondence" msgstr "Correspondentie" -msgid "Cotton Candy is Betty's default theme." -msgstr "Cotton Candy is Betty's standaardthema." +msgid "Cotton Candy is Betty's legacy theme." +msgstr "" msgid "Could not extract {file_path} as a gzip file (*.gz)." msgstr "Kan het bestand {file_path} niet als een gzip-bestand (*.gz) uitpakken." @@ -543,6 +546,9 @@ msgstr "Statische publieke bestanden aan het genereren..." msgid "Generating your site to {output_directory}." msgstr "Bezig je site te genereren naar {output_directory}." +msgid "Go to the front page" +msgstr "" + msgid "HTTP API Documentation" msgstr "HTTP API-documentatie" @@ -564,6 +570,9 @@ msgstr "Het spijt me, schat, maar het lijkt erop dat er geen plaatsen zijn." msgid "I'm sorry, dear, but it seems there are no sources." msgstr "Het spijt me, schat, maar het lijkt erop dat er geen bronnen zijn." +msgid "I'm sorry, dear, but it seems there is nothing to show." +msgstr "Het spijt me, schat, maar het lijkt erop dat er niets is om te laten zien." + msgid "I'm sorry, dear, but it seems this page does not exist." msgstr "Het spijt me, lieverd, maar het lijkt erop dat deze pagina niet bestaat." @@ -587,6 +596,9 @@ msgstr "Ongeldige JSON: {error}." msgid "Invalid YAML: {error}." msgstr "Ongeldige YAML: {error}." +msgid "Keywords" +msgstr "" + msgid "Language" msgstr "Taal" @@ -660,6 +672,9 @@ msgstr "Huwelijk" msgid "Media" msgstr "Media" +msgid "Media references" +msgstr "" + msgid "Menu" msgstr "Menu" @@ -820,6 +835,9 @@ msgstr "Pensioen" msgid "Saved your project to {configuration_file}." msgstr "Configuratie naar \"{configuration_file}\" opgeslagen." +msgid "Search" +msgstr "Zoeken" + msgid "Serve a generated site" msgstr "Serveer een gegenereerde site" @@ -974,9 +992,21 @@ msgstr "De gegevens van deze bron zijn niet beschikbaar vanwege privacyredenen." msgid "Timeline" msgstr "Tijdlijn" +msgid "Toggle primary navigation" +msgstr "" + +msgid "Toggle search" +msgstr "" + +msgid "Toggle translations" +msgstr "" + msgid "Town" msgstr "Town" +msgid "Translations" +msgstr "Vertalingen" + msgid "Trees" msgstr "Stambomen" @@ -1085,7 +1115,7 @@ msgstr "" msgid "" "Your project has no theme enabled. This means your site's pages may look " -"bare. Try the \"cotton-candy\" extension." +"bare. Try the \"raspberry-mint\" extension." msgstr "" "Er is geen thema ingeschakeld voor je project. Dit betekent dat de " "pagina's van je site er leeg uit kunnen zien. Probeer de \"cotton-" diff --git a/betty/assets/locale/uk/betty.po b/betty/assets/locale/uk/betty.po index 3ad0df86d..f8e236363 100644 --- a/betty/assets/locale/uk/betty.po +++ b/betty/assets/locale/uk/betty.po @@ -7,7 +7,7 @@ msgid "" msgstr "" "Project-Id-Version: Betty 0.4\n" "Report-Msgid-Bugs-To: illia@maier.page\n" -"POT-Creation-Date: 2024-12-07 15:18+0000\n" +"POT-Creation-Date: 2024-12-16 21:21+0000\n" "PO-Revision-Date: 2024-11-30 19:00+0000\n" "Last-Translator: Illia Maier \n" "Language: uk\n" @@ -294,6 +294,9 @@ msgstr "Місто" msgid "Clear all caches" msgstr "Очистити всі кеші" +msgid "Close" +msgstr "" + msgid "Conference" msgstr "Конференція" @@ -323,8 +326,8 @@ msgstr "" msgid "Correspondence" msgstr "Листування" -msgid "Cotton Candy is Betty's default theme." -msgstr "Тема за замовчуванням для Betty — Cotton Candy." +msgid "Cotton Candy is Betty's legacy theme." +msgstr "" msgid "Could not extract {file_path} as a gzip file (*.gz)." msgstr "Не вдалося розпакувати {file_path} як gzip файл (*.gz)." @@ -543,6 +546,9 @@ msgstr "Генерація статичних публічних файлів... msgid "Generating your site to {output_directory}." msgstr "Генерація вашого сайту в {output_directory}." +msgid "Go to the front page" +msgstr "" + msgid "HTTP API Documentation" msgstr "документація HTTP API" @@ -564,6 +570,9 @@ msgstr "Вибачте, любий, але, здається, немає міс msgid "I'm sorry, dear, but it seems there are no sources." msgstr "Вибачте, любий, але, здається, немає джерел." +msgid "I'm sorry, dear, but it seems there is nothing to show." +msgstr "" + msgid "I'm sorry, dear, but it seems this page does not exist." msgstr "Вибачте, любий, але, здається, ця сторінка не існує." @@ -585,6 +594,9 @@ msgstr "Недійсний JSON: {error}." msgid "Invalid YAML: {error}." msgstr "Недійсний YAML: {error}." +msgid "Keywords" +msgstr "" + msgid "Language" msgstr "Мова" @@ -660,6 +672,9 @@ msgstr "Шлюб" msgid "Media" msgstr "Медіа" +msgid "Media references" +msgstr "" + msgid "Menu" msgstr "Меню" @@ -820,6 +835,9 @@ msgstr "Вихід на пенсію" msgid "Saved your project to {configuration_file}." msgstr "Проєкт збережено у {configuration_file}." +msgid "Search" +msgstr "" + msgid "Serve a generated site" msgstr "Запустити згенерований сайт" @@ -975,9 +993,21 @@ msgstr "Деталі цього джерела недоступні для за msgid "Timeline" msgstr "Хронологія" +msgid "Toggle primary navigation" +msgstr "" + +msgid "Toggle search" +msgstr "" + +msgid "Toggle translations" +msgstr "" + msgid "Town" msgstr "Містечко" +msgid "Translations" +msgstr "" + msgid "Trees" msgstr "Дерева" @@ -1085,7 +1115,7 @@ msgstr "" msgid "" "Your project has no theme enabled. This means your site's pages may look " -"bare. Try the \"cotton-candy\" extension." +"bare. Try the \"raspberry-mint\" extension." msgstr "" "У вашому проєкті не ввімкнено тему. Це означає, що сторінки вашого сайту " "можуть виглядати пусто. Спробуйте розширення \"cotton-candy\"." diff --git a/betty/locale/translation.py b/betty/locale/translation.py index b7e384569..10fa131be 100644 --- a/betty/locale/translation.py +++ b/betty/locale/translation.py @@ -203,14 +203,16 @@ async def _update_translations( await run_babel( "", "update", - "-i", + "--domain", + "betty", + "--input-file", str(pot_file_path), - "-o", - str(output_po_file_path), - "-l", + "--ignore-obsolete", + "--locale", str(locale_data), - "-D", - "betty", + "--no-fuzzy-matching", + "--output-file", + str(output_po_file_path), ) diff --git a/betty/model/config.py b/betty/model/config.py index 8e3b32e89..4c10fb0e4 100644 --- a/betty/model/config.py +++ b/betty/model/config.py @@ -30,18 +30,18 @@ from betty.serde.dump import Dump, DumpMapping -_EntityT = TypeVar("_EntityT", bound=Entity) +_EntityCoT = TypeVar("_EntityCoT", bound=Entity, covariant=True) @final -class EntityReference(Configuration, Generic[_EntityT]): +class EntityReference(Configuration, Generic[_EntityCoT]): """ Configuration that references an entity from the project's ancestry. """ def __init__( self, - entity_type: PluginIdentifier[_EntityT] | None = None, + entity_type: PluginIdentifier[_EntityCoT] | None = None, entity_id: str | None = None, *, entity_type_is_constrained: bool = False, @@ -61,7 +61,7 @@ def entity_type(self) -> MachineName | None: return self._entity_type @entity_type.setter - def entity_type(self, entity_type: PluginIdentifier[_EntityT]) -> None: + def entity_type(self, entity_type: PluginIdentifier[_EntityCoT]) -> None: if self._entity_type_is_constrained: raise AttributeError( f"The entity type cannot be set, as it is already constrained to {self._entity_type}." @@ -129,7 +129,7 @@ async def validate(self, entity_type_repository: PluginRepository[Entity]) -> No @final class EntityReferenceSequence( - Generic[_EntityT], ConfigurationSequence[EntityReference[_EntityT]] + Generic[_EntityCoT], ConfigurationSequence[EntityReference[_EntityCoT]] ): """ Configuration for a sequence of references to entities from the project's ancestry. @@ -137,9 +137,9 @@ class EntityReferenceSequence( def __init__( self, - entity_references: Iterable[EntityReference[_EntityT]] | None = None, + entity_references: Iterable[EntityReference[_EntityCoT]] | None = None, *, - entity_type_constraint: PluginIdentifier[_EntityT] | None = None, + entity_type_constraint: PluginIdentifier[_EntityCoT] | None = None, ): self._entity_type_constraint = ( None @@ -149,8 +149,8 @@ def __init__( super().__init__(entity_references) @override - def _load_item(self, dump: Dump) -> EntityReference[_EntityT]: - configuration = EntityReference[_EntityT]( + def _load_item(self, dump: Dump) -> EntityReference[_EntityCoT]: + configuration = EntityReference[_EntityCoT]( # Use a dummy entity type for now to satisfy the initializer. # It will be overridden when loading the dump. Entity # type: ignore[arg-type] @@ -162,7 +162,7 @@ def _load_item(self, dump: Dump) -> EntityReference[_EntityT]: return configuration @override - def _pre_add(self, configuration: EntityReference[_EntityT]) -> None: + def _pre_add(self, configuration: EntityReference[_EntityCoT]) -> None: super()._pre_add(configuration) entity_type_constraint = self._entity_type_constraint diff --git a/betty/project/__init__.py b/betty/project/__init__.py index 9faf2bbc7..acf82f9ed 100644 --- a/betty/project/__init__.py +++ b/betty/project/__init__.py @@ -379,7 +379,7 @@ async def _init_extensions(self) -> ProjectExtensions: if theme_count == 0: logging.getLogger().warning( _( - 'Your project has no theme enabled. This means your site\'s pages may look bare. Try the "cotton-candy" extension.' + 'Your project has no theme enabled. This means your site\'s pages may look bare. Try the "raspberry-mint" extension.' ).localize(await self.app.localizer) ) diff --git a/betty/project/extension/_theme.py b/betty/project/extension/_theme.py new file mode 100644 index 000000000..dc96ec692 --- /dev/null +++ b/betty/project/extension/_theme.py @@ -0,0 +1,270 @@ +""" +Common theme functionality. + +The contents of this file should eventually be stabilized and moved to more specific modules. +""" + +from __future__ import annotations + +import re +from collections import defaultdict +from typing import cast, TYPE_CHECKING + +from typing_extensions import override + +from betty.ancestry.event import Event +from betty.ancestry.event_type.event_types import ( + StartOfLifeEventType, + EndOfLifeEventType, +) +from betty.ancestry.person import Person +from betty.ancestry.place import Place +from betty.ancestry.presence_role.presence_roles import Subject +from betty.assertion import assert_str +from betty.assertion.error import AssertionFailed +from betty.config import Configuration +from betty.date import Date, Datey +from betty.functools import unique +from betty.locale.localizable import _ +from betty.model import persistent_id +from betty.privacy import is_public + +if TYPE_CHECKING: + from betty.jinja2 import Filters + from betty.ancestry.file_reference import FileReference + from betty.serde.dump import Dump + from betty.project import Project + from betty.ancestry.presence import Presence + from betty.ancestry.has_file_references import HasFileReferences + from collections.abc import Iterable, Sequence + + +def _is_person_timeline_presence(presence: Presence) -> bool: + if presence.private: + return False + if not presence.event.date: + return False + if not presence.event.date.comparable: + return False + return True + + +def person_timeline_events(person: Person, lifetime_threshold: int) -> Iterable[Event]: + """ + Gather all events for a person's timeline. + """ + yield from unique(_person_timeline_events(person, lifetime_threshold)) + + +def person_descendant_families( + person: Person, +) -> Iterable[tuple[Sequence[Person], Sequence[Person]]]: + """ + Gather a person's families they are a parent in. + """ + parents = {} + children = defaultdict(set) + for child in person.children: + family = tuple(sorted((parent.id for parent in child.parents))) + if family not in parents: + parents[family] = tuple(child.parents) + children[family].add(child) + yield from zip(parents.values(), children.values(), strict=True) + + +def associated_file_references( + has_file_references: HasFileReferences, +) -> Iterable[FileReference]: + """ + Get the associated file references for an entity that has file references. + """ + yield from unique( + _associated_file_references(has_file_references), + key=lambda file_reference: file_reference.file, + ) + + +def _associated_file_references( + has_file_references: HasFileReferences, +) -> Iterable[FileReference]: + yield from has_file_references.file_references + + if isinstance(has_file_references, Event): + for citation in has_file_references.citations: + yield from _associated_file_references(citation) + + if isinstance(has_file_references, Person): + for name in has_file_references.names: + for citation in name.citations: + yield from _associated_file_references(citation) + for presence in has_file_references.presences: + yield from _associated_file_references(presence.event) + + if isinstance(has_file_references, Place): + for event in has_file_references.events: + yield from _associated_file_references(event) + + +def _person_timeline_events(person: Person, lifetime_threshold: int) -> Iterable[Event]: + # Collect all associated events for a person. + # Start with the person's own events for which their presence is public. + for presence in person.presences: + if _is_person_timeline_presence(presence): + assert presence.event is not None + yield presence.event + continue + + # If the person has start- or end-of-life events, we use those to constrain associated people's events. + start_dates = [] + end_dates = [] + for presence in person.presences: + if not _is_person_timeline_presence(presence): + continue + assert presence.event is not None + assert presence.event.date is not None + if not isinstance(presence.role, Subject): + continue + if isinstance(presence.event.event_type, StartOfLifeEventType): + start_dates.append(presence.event.date) + if isinstance(presence.event.event_type, EndOfLifeEventType): + end_dates.append(presence.event.date) + start_date = sorted(start_dates)[0] if start_dates else None + end_date = sorted(end_dates)[0] if end_dates else None + + # If an end-of-life event exists, but no start-of-life event, create a start-of-life date based on the end date, + # minus the lifetime threshold. + if start_date is None and end_date is not None: + if isinstance(end_date, Date): + start_date_reference = end_date + else: + if end_date.end is not None and end_date.end.comparable: + start_date_reference = end_date.end + else: + assert end_date.start is not None + start_date_reference = end_date.start + assert start_date_reference.year is not None + start_date = Date( + start_date_reference.year - lifetime_threshold, + start_date_reference.month, + start_date_reference.day, + start_date_reference.fuzzy, + ) + + # If a start-of-life event exists, but no end-of-life event, create an end-of-life date based on the start date, + # plus the lifetime threshold. + if end_date is None and start_date is not None: + if isinstance(start_date, Date): + end_date_reference = start_date + else: + if start_date.start and start_date.start.comparable: + end_date_reference = start_date.start + else: + assert start_date.end is not None + end_date_reference = start_date.end + assert end_date_reference.year is not None + end_date = Date( + end_date_reference.year + lifetime_threshold, + end_date_reference.month, + end_date_reference.day, + end_date_reference.fuzzy, + ) + + if start_date is None or end_date is None: + reference_dates = sorted( + cast(Datey, presence.event.date) + for presence in person.presences + if _is_person_timeline_presence(presence) + ) + if reference_dates: + if not start_date: + start_date = reference_dates[0] + if not end_date: + end_date = reference_dates[-1] + + if start_date is not None and end_date is not None: + associated_people = filter( + is_public, + ( + # All ancestors. + *person.ancestors, + # All descendants. + *person.descendants, + # All siblings. + *person.siblings, + ), + ) + for associated_person in associated_people: + # For associated events, we are only interested in people's start- or end-of-life events. + for associated_presence in associated_person.presences: + if not isinstance( + associated_presence.event.event_type, + (StartOfLifeEventType, EndOfLifeEventType), + ): + continue + if not persistent_id(associated_presence.event): + continue + if not isinstance(associated_presence.role, Subject): + continue + if not _is_person_timeline_presence(associated_presence): + continue + if not associated_presence.event.date: + continue + if associated_presence.event.date < start_date: + continue + if associated_presence.event.date > end_date: + continue + yield associated_presence.event + + +def jinja2_filters(project: Project) -> Filters: + return { + "person_timeline_events": lambda person: person_timeline_events( + person, project.configuration.lifetime_threshold + ), + "person_descendant_families": person_descendant_families, + "associated_file_references": associated_file_references, + } + + +class ColorConfiguration(Configuration): + """ + Configure a color. + """ + + _HEX_PATTERN = re.compile(r"^#[a-zA-Z0-9]{6}$") + + def __init__(self, hex_value: str): + super().__init__() + self._hex: str + self.hex = hex_value + + def _assert_hex(self, hex_value: str) -> str: + if not self._HEX_PATTERN.match(hex_value): + raise AssertionFailed( + _( + '"{hex_value}" is not a valid hexadecimal color, such as #ffc0cb.' + ).format( + hex_value=hex_value, + ) + ) + return hex_value + + @property + def hex(self) -> str: + """ + The color's hexadecimal value. + """ + return self._hex + + @hex.setter + def hex(self, hex_value: str) -> None: + self._assert_hex(hex_value) + self._hex = hex_value + + @override + def load(self, dump: Dump) -> None: + self._hex = (assert_str() | self._assert_hex)(dump) + + @override + def dump(self) -> Dump: + return self._hex diff --git a/betty/project/extension/cotton_candy/__init__.py b/betty/project/extension/cotton_candy/__init__.py index 0e557f070..7dbd483f7 100644 --- a/betty/project/extension/cotton_candy/__init__.py +++ b/betty/project/extension/cotton_candy/__init__.py @@ -1,39 +1,27 @@ """ -Provide Betty's default theme. +Provide the Cotton Candy theme. """ from __future__ import annotations import json from asyncio import gather -from collections import defaultdict from pathlib import Path -from typing import Iterable, cast, TYPE_CHECKING, final, Self +from typing import TYPE_CHECKING, final, Self import aiofiles from typing_extensions import override -from betty.ancestry.event import Event -from betty.ancestry.event_type.event_types import ( - StartOfLifeEventType, - EndOfLifeEventType, -) -from betty.ancestry.person import Person -from betty.ancestry.place import Place -from betty.ancestry.presence_role.presence_roles import Subject -from betty.date import Date, Datey -from betty.functools import unique from betty.html import CssProvider from betty.jinja2 import ( Jinja2Provider, Filters, ) from betty.locale.localizable import _, static -from betty.model import persistent_id from betty.os import link_or_copy from betty.plugin import ShorthandPluginBase -from betty.privacy import is_public from betty.project.extension import ConfigurableExtension, Theme, Extension +from betty.project.extension._theme import jinja2_filters from betty.project.extension.cotton_candy.config import CottonCandyConfiguration from betty.project.extension.cotton_candy.search import Index from betty.project.extension.maps import Maps @@ -45,9 +33,6 @@ if TYPE_CHECKING: from betty.project import Project - from betty.ancestry.presence import Presence - from betty.ancestry.file_reference import FileReference - from betty.ancestry.has_file_references import HasFileReferences from betty.plugin import PluginIdentifier from betty.event_dispatcher import EventHandlerRegistry from collections.abc import Sequence @@ -58,7 +43,6 @@ """ - _RESULTS_CONTAINER_TEMPLATE = """