From 41bedb9429962b341c1ee286ecd7c1e62e29652e Mon Sep 17 00:00:00 2001 From: Bart Feenstra Date: Mon, 18 Nov 2024 22:49:17 +0000 Subject: [PATCH] The html_lang Jinja2 filter should set the dir HTML attribute (#2220) --- betty/jinja2/filter.py | 24 ++++++++++++++++++- betty/tests/jinja2/test_filter.py | 20 ++++++++++------ .../entity/test_label_place_html_j2.py | 4 ++-- .../entity/test_meta_place_html_j2.py | 6 ++--- .../test_event_dimensions_html_j2.py | 6 ++--- 5 files changed, 44 insertions(+), 16 deletions(-) diff --git a/betty/jinja2/filter.py b/betty/jinja2/filter.py index a3469b5b8..5c369ab32 100644 --- a/betty/jinja2/filter.py +++ b/betty/jinja2/filter.py @@ -45,6 +45,7 @@ UNDETERMINED_LOCALE, SPECIAL_LOCALES, ) +from betty.locale.error import LocaleError from betty.locale.localized import Localized, negotiate_localizeds, LocalizedStr from betty.media_type import MediaType from betty.media_type.media_types import HTML, SVG @@ -117,6 +118,12 @@ def filter_localize( return localizable.localize(context_localizer(context)) +_CHARACTER_ORDER_TO_HTML_LANG_MAP = { + "left-to-right": "ltr", + "right-to-left": "rtl", +} + + @pass_context def filter_html_lang( context: Context, @@ -130,7 +137,22 @@ def filter_html_lang( localizer = context_localizer(context) result: str | Markup = localized if localized.locale != localizer.locale: - result = f'{localized}' + localizer_locale_data = get_data(localizer.locale) + localizer_dir = _CHARACTER_ORDER_TO_HTML_LANG_MAP[ + localizer_locale_data.character_order + ] + try: + localized_locale_data = get_data(localized.locale) + except LocaleError: + localized_dir = "auto" + else: + localized_dir = _CHARACTER_ORDER_TO_HTML_LANG_MAP[ + localized_locale_data.character_order + ] + dir_attribute = ( + f' dir="{localized_dir}"' if localized_dir != localizer_dir else "" + ) + result = f'{localized}' if context.eval_ctx.autoescape: result = Markup(result) return result diff --git a/betty/tests/jinja2/test_filter.py b/betty/tests/jinja2/test_filter.py index 2811fa646..fc25d3c26 100644 --- a/betty/tests/jinja2/test_filter.py +++ b/betty/tests/jinja2/test_filter.py @@ -17,7 +17,6 @@ UNDETERMINED_LOCALE, MULTIPLE_LOCALES, UNCODED_LOCALE, - DEFAULT_LOCALE, ) from betty.locale.localizable import plain from betty.locale.localized import Localized, LocalizedStr @@ -592,16 +591,22 @@ async def test(self) -> None: class TestFilterHtmlLang(TemplateStringTestBase): @pytest.mark.parametrize( - ("expected", "autoescape", "localized_locale"), + ("expected", "autoescape", "localized_locale", "localizer_locale"), [ - ("Hallo, wereld!", True, DEFAULT_LOCALE), - ("Hallo, wereld!", False, DEFAULT_LOCALE), - ('Hallo, wereld!', True, "nl"), - ('Hallo, wereld!', False, "nl"), + ("Hallo, wereld!", True, "nl", "nl"), + ("Hallo, wereld!", False, "nl", "nl"), + ('Hallo, wereld!', True, "nl", "en"), + ('Hallo, wereld!', False, "nl", "en"), + ('Hallo, wereld!', True, "nl", "ar"), + ('Hallo, wereld!', False, "nl", "ar"), ], ) async def test( - self, expected: str, autoescape: bool, localized_locale: str + self, + expected: str, + autoescape: bool, + localized_locale: str, + localizer_locale: str, ) -> None: template = "{{ localized | html_lang }}" localized = LocalizedStr("Hallo, wereld!", locale=localized_locale) @@ -611,6 +616,7 @@ async def test( "localized": localized, }, autoescape=autoescape, + locale=localizer_locale, ) as (actual, _): assert actual == expected diff --git a/betty/tests/project/extension/cotton_candy/assets/templates/entity/test_label_place_html_j2.py b/betty/tests/project/extension/cotton_candy/assets/templates/entity/test_label_place_html_j2.py index b448e514e..213e97873 100644 --- a/betty/tests/project/extension/cotton_candy/assets/templates/entity/test_label_place_html_j2.py +++ b/betty/tests/project/extension/cotton_candy/assets/templates/entity/test_label_place_html_j2.py @@ -22,7 +22,7 @@ class Test(TemplateFileTestBase): ("expected", "data", "locale"), [ ( - 'The Place', + 'The Place', { "entity": Place( id="P0", @@ -54,7 +54,7 @@ class Test(TemplateFileTestBase): "nl", ), ( - 'The Place', + 'The Place', { "entity": Place( id="P0", diff --git a/betty/tests/project/extension/cotton_candy/assets/templates/entity/test_meta_place_html_j2.py b/betty/tests/project/extension/cotton_candy/assets/templates/entity/test_meta_place_html_j2.py index 2c86d4ccc..f2ac306ac 100644 --- a/betty/tests/project/extension/cotton_candy/assets/templates/entity/test_meta_place_html_j2.py +++ b/betty/tests/project/extension/cotton_candy/assets/templates/entity/test_meta_place_html_j2.py @@ -38,7 +38,7 @@ async def test_with_enclosing_place_without_place_context(self) -> None: names=[Name("The All-enclosing Place")], ) Enclosure(enclosee=enclosing_place, encloser=all_enclosing_place) - expected = '
in The Enclosing Place, The All-enclosing Place
' + expected = '
in The Enclosing Place, The All-enclosing Place
' async with self.assert_template_file( data={ "entity": place, @@ -61,7 +61,7 @@ async def test_with_enclosing_place_with_matching_place_context(self) -> None: names=[Name("The All-enclosing Place")], ) Enclosure(enclosee=enclosing_place, encloser=all_enclosing_place) - expected = '
in The Enclosing Place
' + expected = '
in The Enclosing Place
' async with self.assert_template_file( data={ "entity": place, @@ -89,7 +89,7 @@ async def test_with_enclosing_place_with_non_matching_place_context(self) -> Non id="P999", names=[Name("Far Far Away")], ) - expected = '
in The Enclosing Place, The All-enclosing Place
' + expected = '
in The Enclosing Place, The All-enclosing Place
' async with self.assert_template_file( data={ "entity": place, diff --git a/betty/tests/project/extension/cotton_candy/assets/templates/test_event_dimensions_html_j2.py b/betty/tests/project/extension/cotton_candy/assets/templates/test_event_dimensions_html_j2.py index 8092f8875..1bda5fc39 100644 --- a/betty/tests/project/extension/cotton_candy/assets/templates/test_event_dimensions_html_j2.py +++ b/betty/tests/project/extension/cotton_candy/assets/templates/test_event_dimensions_html_j2.py @@ -43,7 +43,7 @@ async def test_with_place(self) -> None: id="P0", names=[Name("The Place")], ) - expected = 'in The Place' + expected = 'in The Place' async with self.assert_template_file( data={ "event": event, @@ -76,7 +76,7 @@ async def test_with_date_and_place(self) -> None: id="P0", names=[Name("The Place")], ) - expected = '1970 in The Place' + expected = '1970 in The Place' async with self.assert_template_file( data={ "event": event, @@ -105,7 +105,7 @@ async def test_embedded(self) -> None: names=[Name("The Place")], ) event.citations.add(Citation(source=Source(name="The Source"))) - expected = '1970 in The Place' + expected = '1970 in The Place' async with self.assert_template_file( data={ "event": event,