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 <noreply@anthropic.com>
This commit is contained in:
Vinta Chen
2026-10-02 19:54:02 +08:00
co-authored by Claude
parent 9cc958162b
commit 38a4641e09
2 changed files with 21 additions and 15 deletions
+17 -11
View File
@@ -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)))
+4 -4
View File
@@ -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 = '<script type="application/ld+json">'
start = sync.index(marker) + len(marker)
end = sync.index("</script>", 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/"},
{