From 9a8ef8ce9f944b5bd48e55ddd59583e4c64ea161 Mon Sep 17 00:00:00 2001 From: jordi fita mas Date: Sun, 6 Aug 2023 05:53:52 +0200 Subject: [PATCH] Add the language switched to the public layout MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The language switcher needs the same information as languageLinks needed, namely the list of locales and the current Path, to construct the URI to all alternate versions. However, in this case i need access to this data in the template context, to build the list of links. At first i use request’s context to hold the list of available locales from application, and it worked, possibly without ill-effects, but i realized that i was doing it just to avoid a new parameter. Or, more precise, an _explicit_ parameter; the context was used to skip the inner functions between app and template.MustRenderPublic, but the parameter was there all the same. Finally, i thought that some handler might want to filter the list of locales to show only the ones that it has a translation of. In that case, i would need to extract the locales from the context, filter it, and create a new request with the updated context. That made little sense, and made me add the explicit locales parameter. Since now the template has the same data as languageLinks, there is little point of having the link in the HTTP response headers, and added the elements to . I thought that maybe i could avoid these as they give the exact same data as the language switch, but Google says nothing of using regular anchors to gather information about localized versions of the document[0], thus i opted to be conservative. One can reason that the has more weight for Google, as most sites with user-generated content, which could contain these anchors, rarely allow users to edit the . [0]: https://developers.google.com/search/docs/specialty/international/localized-versions --- pkg/app/app.go | 5 +-- pkg/app/public.go | 47 ++++++++++++++++++++++++++-- pkg/http/links.go | 49 ------------------------------ pkg/http/request.go | 20 ++++++++++++ pkg/locale/locale.go | 37 ++++++++++++---------- web/templates/public/layout.gohtml | 16 +++++++++- 6 files changed, 102 insertions(+), 72 deletions(-) delete mode 100644 pkg/http/links.go 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" . }}