Fix portal SEO, Korean copy, and favicon

Custom-domain base URLs included the request path, so
canonical/hreflang doubled routes; emit SEO links only with
an absolute origin. Rewrite Korean mismatch strings to avoid
literal particle parentheses, rename HtmlLang to HTMLLang, and
ship public favicons so Vite no longer 404s the fallback icon.

Signed-off-by: Émile Ré <emile@probo.com>
This commit is contained in:
Émile Ré
2026-07-21 18:50:34 +02:00
parent e47091e5b5
commit 59f053e8f9
27 changed files with 89 additions and 20 deletions

View File

@@ -34,7 +34,10 @@ const defaultCompliancePortalLocale = "en"
// SEOFromRequest derives html lang, a self-referencing canonical URL, and
// hreflang alternates (including x-default → English) for the SPA shell.
// Portals are host-routed (slug subdomain / custom domain); the request path
// is already relative to the portal root.
// is already relative to the portal root. pageBaseURL is normalized to its
// origin so a stale path in the base cannot double the route. When pageBaseURL
// has no usable origin, canonical and hreflang are left empty so callers do
// not emit relative SEO links.
func SEOFromRequest(r *http.Request, pageBaseURL string) (htmlLang, canonical string, hreflang []HreflangLink) {
pathname := r.URL.Path
if pathname == "" {
@@ -42,9 +45,16 @@ func SEOFromRequest(r *http.Request, pageBaseURL string) (htmlLang, canonical st
}
locale, rest := splitLocaleFromAppPath(pathname)
htmlLang = locale
canonical = localizedPageURL(pageBaseURL, locale, rest)
origin := portalOrigin(pageBaseURL)
if origin == "" {
// No absolute origin — return lang only; callers must not emit
// relative canonical/hreflang URLs.
return htmlLang, "", nil
}
canonical = localizedPageURL(origin, locale, rest)
locales := iam.SupportedIdentityLocales
@@ -52,18 +62,29 @@ func SEOFromRequest(r *http.Request, pageBaseURL string) (htmlLang, canonical st
for _, loc := range locales {
hreflang = append(hreflang, HreflangLink{
Lang: loc,
Href: localizedPageURL(pageBaseURL, loc, rest),
Href: localizedPageURL(origin, loc, rest),
})
}
hreflang = append(hreflang, HreflangLink{
Lang: "x-default",
Href: localizedPageURL(pageBaseURL, defaultCompliancePortalLocale, rest),
Href: localizedPageURL(origin, defaultCompliancePortalLocale, rest),
})
return htmlLang, canonical, hreflang
}
// portalOrigin returns scheme://host from pageBaseURL, dropping any path,
// query, or fragment. Callers may pass a full request URL by mistake.
func portalOrigin(pageBaseURL string) string {
parsed, err := url.Parse(pageBaseURL)
if err != nil || parsed.Scheme == "" || parsed.Host == "" {
return strings.TrimRight(pageBaseURL, "/")
}
return (&url.URL{Scheme: parsed.Scheme, Host: parsed.Host}).String()
}
func splitLocaleFromAppPath(appPath string) (locale, rest string) {
segments := strings.Split(strings.Trim(appPath, "/"), "/")
if len(segments) == 0 || segments[0] == "" {