From 771f8721cab33374df8b2db4d3393e6aca260410 Mon Sep 17 00:00:00 2001 From: Maxim Lebedev Date: Sat, 11 Nov 2023 23:49:47 +0600 Subject: [PATCH 1/5] :label: Added Translations property for Page domain --- internal/domain/page.go | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/internal/domain/page.go b/internal/domain/page.go index edd9fb3..d960e7b 100644 --- a/internal/domain/page.go +++ b/internal/domain/page.go @@ -3,15 +3,20 @@ package domain import "golang.org/x/text/language" type Page struct { - Language language.Tag - Params map[string]any - File File - Description string - Title string - Content []byte - Resources Resources + Language language.Tag + Params map[string]any + File File + Description string + Title string + Content []byte + Resources Resources + Translations []*Page } func (p Page) IsHome() bool { return p.File.dir == "./" && p.File.translationBaseName == "index" } + +func (p Page) IsTranslated() bool { + return 1 < len(p.Translations) +} From 28228684ef81ae576b96334ad2df3cae97c31800 Mon Sep 17 00:00:00 2001 From: Maxim Lebedev Date: Sat, 11 Nov 2023 23:51:01 +0600 Subject: [PATCH 2/5] :recycle: Use ResourceType enum in GetByType Resources filter as is --- internal/domain/resources.go | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/internal/domain/resources.go b/internal/domain/resources.go index 56851f4..1929ac2 100644 --- a/internal/domain/resources.go +++ b/internal/domain/resources.go @@ -6,14 +6,9 @@ import ( type Resources []*Resource -func (r Resources) GetType(targetType string) Resources { +func (r Resources) GetType(target ResourceType) Resources { out := make(Resources, 0, len(r)) - target, err := ParseResourceType(targetType) - if err != nil { - return out - } - for i := range r { if r[i].resourceType != target { continue From fdaf5be0317538adeb88222d057157abd0ff2923 Mon Sep 17 00:00:00 2001 From: Maxim Lebedev Date: Sat, 11 Nov 2023 23:51:39 +0600 Subject: [PATCH 3/5] :card_file_box: Initialize empty Translations for Page in repository --- internal/page/repository/fs/fs_page.go | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/internal/page/repository/fs/fs_page.go b/internal/page/repository/fs/fs_page.go index 68eed3e..198177b 100644 --- a/internal/page/repository/fs/fs_page.go +++ b/internal/page/repository/fs/fs_page.go @@ -60,12 +60,13 @@ func (repo *fileSystemPageRepository) Get(ctx context.Context, lang language.Tag } return &domain.Page{ - File: domain.NewFile(target), - Language: lang, - Title: data.Title, - Content: data.Content, - Description: data.Description, - Params: data.Params, - Resources: make([]*domain.Resource, 0), + File: domain.NewFile(target), + Language: lang, + Title: data.Title, + Content: data.Content, + Description: data.Description, + Params: data.Params, + Resources: make([]*domain.Resource, 0), + Translations: make([]*domain.Page, 0), }, nil } From 61cc8075db36f37760c4932e1d348ee274ddf73d Mon Sep 17 00:00:00 2001 From: Maxim Lebedev Date: Sat, 11 Nov 2023 23:53:28 +0600 Subject: [PATCH 4/5] :necktie: Search translated versions of current page in Page use case --- internal/page/usecase/page_ucase.go | 35 ++++++++++++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) diff --git a/internal/page/usecase/page_ucase.go b/internal/page/usecase/page_ucase.go index 22453c1..3e54833 100644 --- a/internal/page/usecase/page_ucase.go +++ b/internal/page/usecase/page_ucase.go @@ -3,7 +3,9 @@ package usecase import ( "context" "fmt" + "log" "path" + "strings" "golang.org/x/text/language" @@ -49,7 +51,38 @@ func (ucase *pageUseCase) Do(ctx context.Context, lang language.Tag, p string) ( continue } - out.Resources, _, _ = ucase.statics.Fetch(ctx, out.File.Dir()+"*") + if out.Resources, _, err = ucase.statics.Fetch(ctx, out.File.Dir()+"*"); err != nil { + return out, nil + } + + for _, res := range out.Resources.GetType(domain.ResourceTypePage) { + // TODO(toby3d): simplify this, it's awful + resName := path.Base(res.Key()) + resExt := path.Ext(resName) + resParts := strings.Split(resName[:len(resName)-len(resExt)], ".") + + translationBaseName := strings.Join(resParts[:len(resParts)-1], ".") + if translationBaseName != out.File.TranslationBaseName() { + continue + } + + resLang := language.Make(resParts[len(resParts)-1]) + if resLang == language.Und { + continue + } + + translation, err := ucase.pages.Get(ctx, resLang, targets[i]) + if err != nil { + continue + } + + out.Translations = append(out.Translations, translation) + } + + translations := make([]string, 0) + for i := range out.Translations { + translations = append(translations, out.Translations[i].Language.String()) + } return out, nil } From 3296649caeb200dbbb640359a783d1fa997f7d09 Mon Sep 17 00:00:00 2001 From: Maxim Lebedev Date: Sat, 11 Nov 2023 23:53:58 +0600 Subject: [PATCH 5/5] :building_construction: Use subfolders for translations --- internal/common/common.go | 9 ++++---- main.go | 48 +++++++++++++++++++++++++++++++++++---- 2 files changed, 49 insertions(+), 8 deletions(-) diff --git a/internal/common/common.go b/internal/common/common.go index dc060a3..e818105 100644 --- a/internal/common/common.go +++ b/internal/common/common.go @@ -1,13 +1,14 @@ package common const ( - HeaderAcceptLanguage string = "Accept-Language" - HeaderContentType string = "Content-Type" + HeaderAcceptLanguage string = "Accept-Language" + HeaderContentLanguage string = "Content-Language" + HeaderContentType string = "Content-Type" ) const ( - MIMETextHTML string = "text/html" - MIMETextHTMLCharsetUTF8 string = MIMETextHTML + "; " + charsetUTF8 + MIMETextHTML string = "text/html" + MIMETextHTMLCharsetUTF8 string = MIMETextHTML + "; " + charsetUTF8 ) const charsetUTF8 string = "charset=UTF-8" diff --git a/main.go b/main.go index 6259c66..9b2333c 100644 --- a/main.go +++ b/main.go @@ -18,6 +18,7 @@ import ( "path/filepath" "runtime" "runtime/pprof" + "strings" "syscall" "time" _ "time/tzdata" @@ -38,6 +39,7 @@ import ( "source.toby3d.me/toby3d/home/internal/templateutil" themefsrepo "source.toby3d.me/toby3d/home/internal/theme/repository/fs" themeucase "source.toby3d.me/toby3d/home/internal/theme/usecase" + "source.toby3d.me/toby3d/home/internal/urlutil" ) type ( @@ -81,12 +83,42 @@ func NewApp(ctx context.Context, config *domain.Config) (*App, error) { server := &http.Server{ Addr: config.AddrPort().String(), Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - tags, _, err := language.ParseAcceptLanguage(r.Header.Get(common.HeaderAcceptLanguage)) - if err != nil || len(tags) == 0 { - tags = append(tags, language.English) + head, tail := urlutil.ShiftPath(r.URL.Path) + + if head == "" { + tags, _, err := language.ParseAcceptLanguage(r.Header.Get(common.HeaderAcceptLanguage)) + if err != nil || len(tags) == 0 { + tags = append(tags, language.English) + } + + lang, _, _ := matcher.Match(tags...) + + http.Redirect(w, r, "/"+lang.String()+"/", http.StatusSeeOther) + + return } - lang, _, _ := matcher.Match(tags...) + lang, err := language.Parse(head) + if err != nil || lang == language.Und { + res, err := staticer.Do(r.Context(), r.URL.Path) + if err != nil { + if errors.Is(err, fs.ErrNotExist) { + http.Error(w, err.Error(), http.StatusNotFound) + + return + } + + http.Error(w, err.Error(), http.StatusInternalServerError) + + return + } + + http.ServeContent(w, r, res.Name(), domain.ResourceModTime(res), res) + + return + } + + r.URL.Path = tail s, err := siter.Do(r.Context(), lang) if err != nil { @@ -121,6 +153,14 @@ func NewApp(ctx context.Context, config *domain.Config) (*App, error) { return } + contentLanguage := make([]string, len(p.Translations)) + for i := range p.Translations { + base, _ := p.Translations[i].Language.Base() + contentLanguage[i] = base.String() + } + + w.Header().Set(common.HeaderContentLanguage, strings.Join(contentLanguage, ", ")) + tpl, err := themer.Do(r.Context()) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError)