From 38a4641e0940aac487ea0d8748287e06fd3c508a Mon Sep 17 00:00:00 2001 From: Vinta Chen Date: Fri, 2 Oct 2026 19:54:02 +0800 Subject: [PATCH] feat: improve subcategory page titles and meta descriptions Subcategory titles such as "JWT for Authentication" lacked searchable words, and their boilerplate descriptions never named the listed projects and said "1 projects". Co-Authored-By: Claude --- website/build.py | 28 +++++++++++++++++----------- website/tests/test_build.py | 8 ++++---- 2 files changed, 21 insertions(+), 15 deletions(-) diff --git a/website/build.py b/website/build.py index 28cd7107..e3dd66b4 100644 --- a/website/build.py +++ b/website/build.py @@ -226,13 +226,10 @@ def build_homepage_json_ld(entries: Sequence[TemplateEntry], total_categories: i def category_meta_title(name: str, parent_name: str | None = None) -> str: if parent_name: - title = f"{name} for {parent_name} - Awesome Python" - if len(title) <= 60: - return title - title = f"{parent_name}: {name} - Awesome Python" - if len(title) <= 60: - return title - return f"{name} - Awesome Python" + # Lead with the section's title so the page matches searches like "python jwt library". + title = f"{category_meta_title(parent_name).removesuffix(' - Awesome Python')}: {name}" + # Google shows the site name above each result, so a long title drops it rather than get cut off. + return f"{title} - Awesome Python" if len(title) <= 43 else title # Names ending in one of these nouns already say what the entries are. noun = "" if name.rsplit(" ", 1)[-1] in PLURAL_NOUNS else " Libraries" prefix = "" if name.startswith("Python ") else "Python " @@ -242,15 +239,19 @@ def category_meta_title(name: str, parent_name: str | None = None) -> str: return f"{name} - Awesome Python" -def category_meta_description(name: str, entry_count: int, description: str, parent_name: str | None = None) -> str: - target = f"{name} for {parent_name}" if parent_name else name - count_sentence = f"Explore {entry_count} curated Python projects in {target}." +def category_meta_description(name: str, entry_count: int, description: str) -> str: + count_sentence = f"Explore {entry_count} curated Python project{'s' if entry_count != 1 else ''} in {name}." if description: lead = description if description.endswith((".", "!", "?")) else f"{description}." return f"{lead} {count_sentence}" return f"{count_sentence} Part of the Awesome Python catalog." +def subcategory_meta_description(name: str, parent_name: str, entry_names: Sequence[str]) -> str: + names = entry_names[0] if len(entry_names) == 1 else f"{', '.join(entry_names[:-1])}{',' if len(entry_names) > 2 else ''} and {entry_names[-1]}" + return f"The {name} picks in Awesome Python's {parent_name} list: {names}." + + def load_category_intro(path: Path) -> tuple[str, str, str, str]: """Render a category intro file to HTML, split at the end of its "How to choose:" list. @@ -775,7 +776,12 @@ def build(repo_root: Path) -> None: parent_name = parent_category["name"] if parent_category else None category_title = category_meta_title(category["name"], parent_name) intro_html, guide_html, intro_lead, _ = load_category_intro(website / "data" / "category_intros" / f"{current_path.removeprefix('/categories/').strip('/')}.md") - category_description = intro_lead or category_meta_description(category["name"], len(entries), category["description"], parent_name) + if intro_lead: + category_description = intro_lead + elif parent_name: + category_description = subcategory_meta_description(category["name"], parent_name, [e["name"] for e in entries]) + else: + category_description = category_meta_description(category["name"], len(entries), category["description"]) breadcrumbs = [("Awesome Python", SITE_URL)] if parent_category: breadcrumbs.append((parent_category["name"], category_public_url(parent_category))) diff --git a/website/tests/test_build.py b/website/tests/test_build.py index 5e1df4eb..b60ef26d 100644 --- a/website/tests/test_build.py +++ b/website/tests/test_build.py @@ -794,7 +794,7 @@ class TestBuild: assert collection["name"] == "Python AI & ML Libraries" assert collection["@id"] == "https://awesome-python.com/categories/ai-ml/" assert collection["url"] == "https://awesome-python.com/categories/ai-ml/" - assert collection["description"] == "Explore 1 curated Python projects in AI & ML. Part of the Awesome Python catalog." + assert collection["description"] == "Explore 1 curated Python project in AI & ML. Part of the Awesome Python catalog." def test_category_title_skips_libraries_after_plural_noun(self, tmp_path): readme = textwrap.dedent("""\ @@ -904,14 +904,14 @@ class TestBuild: parser = HeadMetadataParser() parser.feed(sync) - assert parser.title.strip() == "Synchronous for Web Frameworks - Awesome Python" - assert parser.meta_by_name["description"] == "Explore 1 curated Python projects in Synchronous for Web Frameworks. Part of the Awesome Python catalog." + assert parser.title.strip() == "Python Web Frameworks: Synchronous - Awesome Python" + assert parser.meta_by_name["description"] == "The Synchronous picks in Awesome Python's Web Frameworks list: django." marker = '", start) graph = {node["@type"]: node for node in json.loads(sync[start:end])["@graph"]} - assert graph["CollectionPage"]["name"] == "Synchronous for Web Frameworks" + assert graph["CollectionPage"]["name"] == "Python Web Frameworks: Synchronous" assert graph["BreadcrumbList"]["itemListElement"] == [ {"@type": "ListItem", "position": 1, "name": "Awesome Python", "item": "https://awesome-python.com/"}, {