feat(i18n): every server A→Z list in the reader's alphabet (E30 S3)
The Postgres image is Alpine, whose en_US.utf8 is byte order: "Életrajzi" and "Ötödik" sorted after "Zorró" in the feed, the channel lists, the Plex grid, its genres and collections. `app/collation.py` names the ICU collation of the reader's language (hu-x-icu puts "Ö" after "O" and "Cs" after "Cu"), wraps every text ORDER BY with it - the feed's keyset cursor compares with the same expression, so paging walks the order it sorted in - and orders the lists built in Python (genre and studio facets, collection members, the discovery re-sort) through the same collation. Plex title tiebreaks use the shown (HU) title.
This commit is contained in:
@@ -15,6 +15,7 @@ from datetime import date, datetime, timedelta, timezone
|
||||
from sqlalchemy import BigInteger, Select, and_, cast, false, func, or_, select
|
||||
from sqlalchemy.orm import Session, aliased
|
||||
|
||||
from app.collation import collated
|
||||
from app.models import (
|
||||
LIVE_OR_UPCOMING,
|
||||
BlockedChannel,
|
||||
@@ -478,8 +479,10 @@ def feed_years(db: Session, user: User, **params) -> list[dict]:
|
||||
# replaces OFFSET/LIMIT: deep scrolling no longer scans-and-discards skipped rows,
|
||||
# and the page boundary is stable even as the scheduler ingests new videos mid-scroll
|
||||
# (OFFSET would shift and duplicate/skip rows).
|
||||
def sort_keys(sort: str, seed: int, rank_expr=None) -> list[dict]:
|
||||
def sort_keys(sort: str, seed: int, rank_expr=None, lang: str | None = None) -> list[dict]:
|
||||
"""`lang` orders the title sorts in the reader's alphabet (`collation`); the other keys ignore it."""
|
||||
pub = {"expr": Video.published_at, "asc": False, "nullable": False, "kind": "dt"}
|
||||
title = collated(Video.title, lang)
|
||||
table: dict[str, list[dict]] = {
|
||||
"newest": [pub],
|
||||
"oldest": [{**pub, "asc": True}],
|
||||
@@ -491,10 +494,8 @@ def sort_keys(sort: str, seed: int, rank_expr=None) -> list[dict]:
|
||||
"duration_asc": [
|
||||
{"expr": Video.duration_seconds, "asc": True, "nullable": True, "kind": "num"}
|
||||
],
|
||||
"title": [{"expr": func.lower(Video.title), "asc": True, "nullable": True, "kind": "str"}],
|
||||
"title_desc": [
|
||||
{"expr": func.lower(Video.title), "asc": False, "nullable": True, "kind": "str"}
|
||||
],
|
||||
"title": [{"expr": title, "asc": True, "nullable": True, "kind": "str"}],
|
||||
"title_desc": [{"expr": title, "asc": False, "nullable": True, "kind": "str"}],
|
||||
"subscribers": [
|
||||
{"expr": Channel.subscriber_count, "asc": False, "nullable": True, "kind": "num"}
|
||||
],
|
||||
|
||||
Reference in New Issue
Block a user