diff --git a/pkg/app/app.go b/pkg/app/app.go index 4e614f9..0a3fbf8 100644 --- a/pkg/app/app.go +++ b/pkg/app/app.go @@ -116,10 +116,7 @@ func (h *App) ServeHTTP(w http.ResponseWriter, r *http.Request) { return } user.Locale = urlLocale - if r.Method == http.MethodGet || r.Method == http.MethodHead { - w = httplib.LanguageLinks(w, false, r.Host, r.URL.Path, h.locales) - } - h.public.Handler(user, company, conn).ServeHTTP(w, r) + h.public.Handler(user, company, h.locales, conn).ServeHTTP(w, r) } } } diff --git a/pkg/app/public.go b/pkg/app/public.go index 5afd6ef..c8d8fa8 100644 --- a/pkg/app/public.go +++ b/pkg/app/public.go @@ -6,11 +6,13 @@ package app import ( + "fmt" "net/http" "dev.tandem.ws/tandem/camper/pkg/auth" "dev.tandem.ws/tandem/camper/pkg/database" httplib "dev.tandem.ws/tandem/camper/pkg/http" + "dev.tandem.ws/tandem/camper/pkg/locale" "dev.tandem.ws/tandem/camper/pkg/template" ) @@ -20,15 +22,56 @@ func newPublicHandler() *publicHandler { return &publicHandler{} } -func (h *publicHandler) Handler(user *auth.User, company *auth.Company, conn *database.Conn) http.Handler { +func (h *publicHandler) Handler(user *auth.User, company *auth.Company, locales locale.Locales, conn *database.Conn) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { var head string head, r.URL.Path = httplib.ShiftPath(r.URL.Path) switch head { case "": - template.MustRenderPublic(w, r, user, company, "home.gohtml", nil) + page := newHomePage() + page.MustRender(w, r, user, company, locales) default: http.NotFound(w, r) } }) } + +type homePage struct { + *PublicPage +} + +func newHomePage() *homePage { + return &homePage{newPublicPage("home.gohtml")} +} + +type PublicPage struct { + template string + LocalizedAlternates []*LocalizedAlternate +} + +func newPublicPage(template string) *PublicPage { + return &PublicPage{ + template: template, + } +} + +func (p *PublicPage) MustRender(w http.ResponseWriter, r *http.Request, user *auth.User, company *auth.Company, locales locale.Locales) { + schema := httplib.Protocol(r) + authority := httplib.Host(r) + _, path := httplib.ShiftPath(r.RequestURI) + for _, l := range locales { + p.LocalizedAlternates = append(p.LocalizedAlternates, &LocalizedAlternate{ + Lang: l.Language.String(), + Endonym: l.Endonym, + HRef: fmt.Sprintf("%s://%s/%s%s", schema, authority, l.Language, path), + }) + } + + template.MustRenderPublic(w, r, user, company, p.template, p) +} + +type LocalizedAlternate struct { + Lang string + HRef string + Endonym string +} diff --git a/pkg/http/links.go b/pkg/http/links.go deleted file mode 100644 index aad70ce..0000000 --- a/pkg/http/links.go +++ /dev/null @@ -1,49 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2023 jordi fita mas - * SPDX-License-Identifier: AGPL-3.0-only - */ - -package http - -import ( - "fmt" - "net/http" - - "dev.tandem.ws/tandem/camper/pkg/locale" -) - -type languageLinks struct { - http.ResponseWriter - schemaAuthority string - path string - locales locale.Locales - wroteHeader bool -} - -func (w *languageLinks) WriteHeader(statusCode int) { - if statusCode >= 200 && statusCode < 300 { - for k := range w.locales { - tag := k.String() - w.Header().Add("Link", fmt.Sprintf(`<%[1]s/%[2]s%[3]s>; rel="alternate"; hreflang="%[2]s"`, w.schemaAuthority, tag, w.path)) - } - } - w.wroteHeader = true - w.ResponseWriter.WriteHeader(statusCode) -} - -func (w *languageLinks) Write(data []byte) (int, error) { - if !w.wroteHeader { - w.WriteHeader(http.StatusOK) - } - return w.ResponseWriter.Write(data) -} - -func LanguageLinks(w http.ResponseWriter, https bool, authority string, path string, locales locale.Locales) http.ResponseWriter { - var schema string - if https { - schema = "https" - } else { - schema = "http" - } - return &languageLinks{w, schema + "://" + authority, path, locales, false} -} diff --git a/pkg/http/request.go b/pkg/http/request.go index f4894b4..c317fd0 100644 --- a/pkg/http/request.go +++ b/pkg/http/request.go @@ -42,3 +42,23 @@ func MethodNotAllowed(w http.ResponseWriter, _ *http.Request, allowed ...string) w.Header().Set("Allow", strings.Join(allowed, ", ")) http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) } + +func Host(r *http.Request) string { + host := r.Header.Get("X-Forwarded-Host") + if host == "" { + host = r.Host + } + return host +} + +func IsHTTPS(r *http.Request) bool { + return r.Header.Get("X-Forwarded-Proto") == "https" +} + +func Protocol(r *http.Request) string { + if IsHTTPS(r) { + return "https" + } else { + return "http" + } +} diff --git a/pkg/locale/locale.go b/pkg/locale/locale.go index 8b70370..8a3fc92 100644 --- a/pkg/locale/locale.go +++ b/pkg/locale/locale.go @@ -7,6 +7,7 @@ package locale import ( "context" + "github.com/leonelquinteros/gotext" "golang.org/x/text/language" @@ -17,6 +18,7 @@ type Locale struct { *gotext.Locale CurrencyPattern string Language language.Tag + Endonym string } type Locales map[language.Tag]*Locale @@ -36,23 +38,15 @@ func GetAll(ctx context.Context, db *database.DB) (Locales, error) { if err != nil { return nil, err } - locales := map[language.Tag]*Locale{} + locales := Locales{} for _, lang := range availableLanguages { - locale := newLocale(lang) + locale := lang.locale() locale.AddDomain("camper") locales[lang.tag] = locale } return locales, nil } -func newLocale(lang availableLanguage) *Locale { - return &Locale{ - gotext.NewLocale("locale", lang.tag.String()), - lang.currencyPattern, - lang.tag, - } -} - func (l *Locale) Gettext(str string) string { return l.GetD(l.GetDomain(), str) } @@ -85,25 +79,27 @@ func Match(acceptLanguage string, locales Locales, matcher language.Matcher) *Lo type availableLanguage struct { tag language.Tag + endonym string currencyPattern string } -func getAvailableLanguages(ctx context.Context, db *database.DB) ([]availableLanguage, error) { - rows, err := db.Query(ctx, "select lang_tag, currency_pattern from language where selectable") +func getAvailableLanguages(ctx context.Context, db *database.DB) ([]*availableLanguage, error) { + rows, err := db.Query(ctx, "select lang_tag, endonym, currency_pattern from language where selectable") if err != nil { return nil, err } defer rows.Close() - var languages []availableLanguage + var languages []*availableLanguage for rows.Next() { + lang := &availableLanguage{} var langTag string - var currencyPattern string - err = rows.Scan(&langTag, ¤cyPattern) + err = rows.Scan(&langTag, &lang.endonym, &lang.currencyPattern) if err != nil { return nil, err } - languages = append(languages, availableLanguage{language.MustParse(langTag), currencyPattern}) + lang.tag = language.MustParse(langTag) + languages = append(languages, lang) } if rows.Err() != nil { return nil, rows.Err() @@ -111,3 +107,12 @@ func getAvailableLanguages(ctx context.Context, db *database.DB) ([]availableLan return languages, nil } + +func (lang *availableLanguage) locale() *Locale { + return &Locale{ + gotext.NewLocale("locale", lang.tag.String()), + lang.currencyPattern, + lang.tag, + lang.endonym, + } +} diff --git a/web/templates/public/layout.gohtml b/web/templates/public/layout.gohtml index 4b31e58..ec98d54 100644 --- a/web/templates/public/layout.gohtml +++ b/web/templates/public/layout.gohtml @@ -2,6 +2,7 @@ SPDX-FileCopyrightText: 2023 jordi fita mas SPDX-License-Identifier: AGPL-3.0-only --> +{{- /*gotype: dev.tandem.ws/tandem/camper/pkg/app.PublicPage*/ -}} @@ -9,12 +10,25 @@ {{ template "title" . }} — {{( gettext "Campsite Montagut" )}} - {{ block "head" . }}{{ end }} + {{ range .LocalizedAlternates -}} + + {{ end }} + {{- block "head" . }}{{ end }}
{{( gettext "Skip to main content" )}}

{{( gettext "Campsite Montagut" )}}

+ {{ if .LocalizedAlternates -}} + + {{- end }}
{{- template "content" . }}