package web import ( "strings" "khosra/internal/content" ) // resolution is what a request path means: which bundle key, in which language, or where to send the // client instead. type resolution struct { key string lang string // redirect is the canonical path when the request named a non-canonical one. Non-empty means answer // with a permanent redirect and nothing else. redirect string } // resolve maps a request path to a bundle key and language. // // Two cases, which is why this is a resolver rather than an inline branch: the default locale lives at // the root of the URL space, every other language under a prefix on the same key (ADR-0009). `/en/…` is // never a live URL — it redirects to the root form so the space cannot fork. The canonical form ends in // a slash (ADR-0008). // // A language prefix wins over a section of the same name, so a site with Bengali content cannot also // have a section called `bn` (content-model.md). func resolve(path string, site *content.Site) (resolution, bool) { if path == "" || path[0] != '/' { return resolution{}, false } trimmed := strings.Trim(path, "/") if trimmed == "" { return resolution{}, false } lang := content.DefaultLang key := content.Normalise(trimmed) if head, rest, found := strings.Cut(key, "/"); found && head != "" { switch { case head == content.DefaultLang: // /en/… is a second spelling of the root form; send the client to the real one. return resolution{redirect: content.URL(rest, content.DefaultLang)}, true case site.HasLang(head): lang, key = head, rest } } else if key == content.DefaultLang { return resolution{redirect: "/"}, true } if !strings.HasSuffix(path, "/") { return resolution{key: key, lang: lang, redirect: content.URL(key, lang)}, true } return resolution{key: key, lang: lang}, true }