Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[HNT-295] fix section date localization #733

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions merino/curated_recommendations/localization.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
"""Hardcoded localized strings."""

from datetime import datetime

from babel.dates import format_date

from merino.curated_recommendations.corpus_backends.protocol import ScheduledSurfaceId
import logging

Expand Down Expand Up @@ -72,3 +76,28 @@ def get_translation(surface_id: ScheduledSurfaceId, topic: str, default_topic: s
return default_topic

return translations[topic]


# Localization for Babel date formats based on surface_id.
SURFACE_ID_TO_LOCALE = {
ScheduledSurfaceId.NEW_TAB_EN_US: "en_US",
ScheduledSurfaceId.NEW_TAB_EN_GB: "en_GB",
ScheduledSurfaceId.NEW_TAB_DE_DE: "de_DE",
ScheduledSurfaceId.NEW_TAB_FR_FR: "fr_FR",
ScheduledSurfaceId.NEW_TAB_ES_ES: "es_ES",
ScheduledSurfaceId.NEW_TAB_IT_IT: "it_IT",
ScheduledSurfaceId.NEW_TAB_EN_INTL: "en_IN", # En-Intl is primarily used in India.
}
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The section experiment only runs in the US and Germany, but I thought it was better to implement this for all markets that we serve.



def get_localized_date(surface_id: ScheduledSurfaceId, date: datetime) -> str:
"""Return a localized date string for the given ScheduledSurfaceId.

Args:
surface_id (ScheduledSurfaceId): The New Tab surface ID.
date (datetime): The date to be localized.

Returns:
str: Localized date string, for example "December 17, 2024".
"""
return format_date(date, format="long", locale=SURFACE_ID_TO_LOCALE.get(surface_id, "en_US"))
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just confirming that this defaults to en-US?

14 changes: 10 additions & 4 deletions merino/curated_recommendations/provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,9 @@
import logging
import time
import re
from datetime import datetime
from typing import cast

from merino.curated_recommendations import ExtendedExpirationCorpusBackend
from merino.curated_recommendations import ExtendedExpirationCorpusBackend, CorpusApiBackend
from merino.curated_recommendations.corpus_backends.protocol import (
CorpusBackend,
ScheduledSurfaceId,
Expand All @@ -21,7 +20,11 @@
layout_4_large,
layout_6_tiles,
)
from merino.curated_recommendations.localization import get_translation, LOCALIZED_SECTION_TITLES
from merino.curated_recommendations.localization import (
get_translation,
LOCALIZED_SECTION_TITLES,
get_localized_date,
)
from merino.curated_recommendations.prior_backends.protocol import PriorBackend
from merino.curated_recommendations.protocol import (
Locale,
Expand Down Expand Up @@ -358,12 +361,15 @@ async def get_sections(
remaining_recs = general_recs[top_stories_count:]

# Create "Today's top stories" section with the first 6 recommendations
surface_date = CorpusApiBackend.get_scheduled_surface_date(
CorpusApiBackend.get_surface_timezone(surface_id)
)
feeds = CuratedRecommendationsFeed(
news_section=Section(
receivedFeedRank=0,
recommendations=news_recs,
title=get_translation(surface_id, "news-section", "In the News"),
subtitle=datetime.now().strftime("%B %d, %Y"), # e.g., "October 24, 2024"
subtitle=get_localized_date(surface_id, surface_date), # e.g., "October 24, 2024"
layout=layout_4_large,
),
top_stories_section=Section(
Expand Down
18 changes: 16 additions & 2 deletions poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ pydantic = "^2.1.0"
scipy = "^1.14.1"
orjson = "^3.10.7"
tenacity = "^9.0.0"
babel = "^2.16.0"

[tool.poetry.group.jobs.dependencies]
# Jobs specific dependecies required on top of some of the main dependencies above
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1446,7 +1446,7 @@ async def test_curated_recommendations_with_sections_feed_boost_followed_section

@pytest.mark.asyncio
@pytest.mark.parametrize(
"locale, expected_titles",
"locale, expected_titles, expected_news_subtitle",
[
(
"en-US",
Expand All @@ -1457,6 +1457,7 @@ async def test_curated_recommendations_with_sections_feed_boost_followed_section
"education": "Education",
"sports": "Sports",
},
"December 17, 2024", # Frozen time 2024-12-18 6am UTC = 2024-12-17 10pm PST
),
(
"de-DE",
Expand All @@ -1467,10 +1468,12 @@ async def test_curated_recommendations_with_sections_feed_boost_followed_section
"education": "Bildung",
"sports": "Sport",
},
"18. Dezember 2024", # Frozen time 2024-12-18 6am UTC = 2024-12-18 7am CET
),
],
)
async def test_sections_feed_titles(self, locale, expected_titles):
@freezegun.freeze_time("2024-12-18 06:00:00", tz_offset=0)
async def test_sections_feed_titles(self, locale, expected_titles, expected_news_subtitle):
"""Test the curated recommendations endpoint 'sections' have the expected (sub)titles."""
async with AsyncClient(app=app, base_url="http://test") as ac:
# Mock the endpoint to request the sections feed
Expand All @@ -1495,7 +1498,7 @@ async def test_sections_feed_titles(self, locale, expected_titles):
# Ensure "Today's top stories" is present with a valid date subtitle
news_section = data["feeds"].get("news_section")
assert news_section is not None
assert news_section["subtitle"] is not None
assert news_section["subtitle"] == expected_news_subtitle


class TestExtendedExpiration:
Expand Down
23 changes: 22 additions & 1 deletion tests/unit/curated_recommendations/test_localization.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
"""Tests covering merino/curated_recommendations/localization.py"""

from merino.curated_recommendations.localization import get_translation
from datetime import datetime

import pytest

from merino.curated_recommendations.corpus_backends.protocol import ScheduledSurfaceId
from merino.curated_recommendations.localization import get_translation, get_localized_date


def test_get_translation_existing_translation():
Expand All @@ -28,3 +32,20 @@ def test_get_translation_non_existing_slug(caplog):
errors = [r for r in caplog.records if r.levelname == "ERROR"]
assert len(errors) == 1
assert "Missing or empty translation for topic" in errors[0].message


@pytest.mark.parametrize(
"surface_id, expected_output",
[
(ScheduledSurfaceId.NEW_TAB_EN_US, "December 17, 2024"),
(ScheduledSurfaceId.NEW_TAB_EN_GB, "17 December 2024"),
(ScheduledSurfaceId.NEW_TAB_DE_DE, "17. Dezember 2024"),
(ScheduledSurfaceId.NEW_TAB_FR_FR, "17 décembre 2024"),
(ScheduledSurfaceId.NEW_TAB_ES_ES, "17 de diciembre de 2024"),
(ScheduledSurfaceId.NEW_TAB_IT_IT, "17 dicembre 2024"),
(ScheduledSurfaceId.NEW_TAB_EN_INTL, "17 December 2024"),
],
)
def test_get_localized_date_output(surface_id: ScheduledSurfaceId, expected_output: str):
"""Test that get_localized_date returns correctly formatted date strings."""
assert get_localized_date(surface_id, date=datetime(2024, 12, 17)) == expected_output