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 _. 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 }