feat: JSON-LD graph, breadcrumbs and hreflang in the resolve answer (tracer)
- classes/schema builds one @graph per page (WebPage, article, profile, service, BreadcrumbList, VideoObject, WebSite, publisher) linked by @id - breadcrumb trail through the primary parent chain, hreflang alternates with x-default, html_lang and og:locale in the resolve answer - migration 202610280004 adds the publisher, local business, home crumb and pagination settings columns
This commit is contained in:
119
classes/alternates.go
Normal file
119
classes/alternates.go
Normal file
@@ -0,0 +1,119 @@
|
||||
package classes
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"git.golem15.com/golem15/summercms/modules/pact"
|
||||
)
|
||||
|
||||
// OGLocalesConfigKey maps locale codes to og:locale values (D-55); unset
|
||||
// codes fall back to DefaultOGLocales, else <code>_<CODE>.
|
||||
const OGLocalesConfigKey = "golem15.seo.og_locales"
|
||||
|
||||
// SearchURLConfigKey is the site search address with the
|
||||
// {search_term_string} placeholder (P33-25); empty means no SearchAction.
|
||||
const SearchURLConfigKey = "golem15.seo.search_url"
|
||||
|
||||
// DefaultOGLocales are the og:locale values of the common locales.
|
||||
var DefaultOGLocales = map[string]string{"pl": "pl_PL", "en": "en_US", "de": "de_DE"}
|
||||
|
||||
// Alternate is one hreflang alternate of a page (D-55): a language tag or
|
||||
// x-default and the absolute URL.
|
||||
type Alternate struct {
|
||||
Hreflang string `json:"hreflang"`
|
||||
URL string `json:"url"`
|
||||
}
|
||||
|
||||
// LangTag turns a locale code into its language tag: pl stays pl, pt_BR
|
||||
// and pt-br become pt-BR (the region is kept when the locale has one).
|
||||
func LangTag(locale string) string {
|
||||
locale = strings.TrimSpace(strings.ReplaceAll(locale, "_", "-"))
|
||||
lang, region, ok := strings.Cut(locale, "-")
|
||||
if !ok || region == "" {
|
||||
return strings.ToLower(lang)
|
||||
}
|
||||
return strings.ToLower(lang) + "-" + strings.ToUpper(region)
|
||||
}
|
||||
|
||||
// OGLocale is the og:locale value of locale: the configured value, else
|
||||
// the default table, else the language with its region (pt_BR) or the
|
||||
// language doubled (fr_FR).
|
||||
func (s *Service) OGLocale(locale string) string {
|
||||
tag := LangTag(locale)
|
||||
if tag == "" {
|
||||
return ""
|
||||
}
|
||||
if cfg := s.Config(); cfg != nil {
|
||||
for _, key := range []string{locale, tag, strings.ToLower(tag)} {
|
||||
if v := strings.TrimSpace(cfg.String(OGLocalesConfigKey + "." + key)); v != "" {
|
||||
return v
|
||||
}
|
||||
}
|
||||
}
|
||||
if v, ok := DefaultOGLocales[tag]; ok {
|
||||
return v
|
||||
}
|
||||
lang, region, ok := strings.Cut(tag, "-")
|
||||
if ok {
|
||||
return lang + "_" + region
|
||||
}
|
||||
return lang + "_" + strings.ToUpper(lang)
|
||||
}
|
||||
|
||||
// translatedLocales lists the locales of rec with a public address, in the
|
||||
// record's order.
|
||||
func translatedLocales(rec *pact.ContentRecord) []pact.ContentLocale {
|
||||
if rec == nil {
|
||||
return nil
|
||||
}
|
||||
var out []pact.ContentLocale
|
||||
seen := map[string]bool{}
|
||||
for _, l := range rec.Locales {
|
||||
if strings.TrimSpace(l.URL) == "" || l.Locale == "" || seen[l.Locale] {
|
||||
continue
|
||||
}
|
||||
seen[l.Locale] = true
|
||||
out = append(out, l)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// Alternates lists the hreflang alternates of a record (D-55 to D-57):
|
||||
// each translated locale with its absolute URL, the page itself included,
|
||||
// then x-default on the default locale's URL. A record translated into one
|
||||
// locale only has none; a locale without a translation is absent.
|
||||
func Alternates(site string, rec *pact.ContentRecord, defaultLocale string) []Alternate {
|
||||
locales := translatedLocales(rec)
|
||||
if len(locales) < 2 {
|
||||
return []Alternate{}
|
||||
}
|
||||
out := make([]Alternate, 0, len(locales)+1)
|
||||
xDefault := ""
|
||||
for _, l := range locales {
|
||||
u := AbsoluteURL(site, l.URL)
|
||||
out = append(out, Alternate{Hreflang: LangTag(l.Locale), URL: u})
|
||||
if l.Locale == defaultLocale {
|
||||
xDefault = u
|
||||
}
|
||||
}
|
||||
if xDefault != "" {
|
||||
out = append(out, Alternate{Hreflang: "x-default", URL: xDefault})
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// OGLocaleAlternates are the og:locale:alternate values of a record: one
|
||||
// per other translated locale.
|
||||
func (s *Service) OGLocaleAlternates(rec *pact.ContentRecord, locale string) []string {
|
||||
out := []string{}
|
||||
locales := translatedLocales(rec)
|
||||
if len(locales) < 2 {
|
||||
return out
|
||||
}
|
||||
for _, l := range locales {
|
||||
if l.Locale != locale {
|
||||
out = append(out, s.OGLocale(l.Locale))
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
Reference in New Issue
Block a user