Просмотр исходного кода

refactor(reader): move language normalization out of model

NormalizeLanguage lived in the model package but is a parse-time
input-cleaning helper: all of its callers are the feed-format adapters,
nothing in model uses it, and as a free function it never enforced a
model invariant. Parse-time normalization helpers belong under
internal/reader alongside date, sanitizer, and urlcleaner.

Move it to a new internal/reader/language package and rename it to
Normalize so call sites read language.Normalize(...). No behaviour
change.
Fred 1 день назад
Родитель
Сommit
6972be2e85

+ 4 - 3
internal/reader/atom/atom_03_adapter.go

@@ -11,6 +11,7 @@ import (
 	"miniflux.app/v2/internal/crypto"
 	"miniflux.app/v2/internal/model"
 	"miniflux.app/v2/internal/reader/date"
+	"miniflux.app/v2/internal/reader/language"
 	"miniflux.app/v2/internal/reader/sanitizer"
 	"miniflux.app/v2/internal/urllib"
 )
@@ -47,7 +48,7 @@ func (a *atom03Adapter) buildFeed(baseURL string) *model.Feed {
 		feed.Title = feed.SiteURL
 	}
 
-	feed.Language = model.NormalizeLanguage(a.atomFeed.Language)
+	feed.Language = language.Normalize(a.atomFeed.Language)
 
 	for _, atomEntry := range a.atomFeed.Entries {
 		entry := model.NewEntry()
@@ -55,9 +56,9 @@ func (a *atom03Adapter) buildFeed(baseURL string) *model.Feed {
 		// Populate the entry language. xml:lang applies to the whole
 		// subtree it is declared on, so an entry without its own
 		// xml:lang inherits the feed-level value.
-		entry.Language = model.NormalizeLanguage(atomEntry.Language)
+		entry.Language = language.Normalize(atomEntry.Language)
 		if entry.Language == "" {
-			entry.Language = model.NormalizeLanguage(a.atomFeed.Language)
+			entry.Language = language.Normalize(a.atomFeed.Language)
 		}
 
 		// Populate the entry URL.

+ 4 - 3
internal/reader/atom/atom_10_adapter.go

@@ -12,6 +12,7 @@ import (
 	"miniflux.app/v2/internal/crypto"
 	"miniflux.app/v2/internal/model"
 	"miniflux.app/v2/internal/reader/date"
+	"miniflux.app/v2/internal/reader/language"
 	"miniflux.app/v2/internal/reader/sanitizer"
 	"miniflux.app/v2/internal/urllib"
 )
@@ -51,7 +52,7 @@ func (a *atom10Adapter) buildFeed(baseURL string) *model.Feed {
 	// Populate the feed description.
 	feed.Description = a.atomFeed.Subtitle.body()
 
-	feed.Language = model.NormalizeLanguage(a.atomFeed.Language)
+	feed.Language = language.Normalize(a.atomFeed.Language)
 
 	// Populate the feed icon.
 	for _, value := range []string{a.atomFeed.Icon, a.atomFeed.Logo} {
@@ -114,9 +115,9 @@ func (a *atom10Adapter) populateEntries(siteURL string) model.Entries {
 		// Populate the entry language. xml:lang applies to the whole
 		// subtree it is declared on, so an entry without its own
 		// xml:lang inherits the feed-level value.
-		entry.Language = model.NormalizeLanguage(atomEntry.Language)
+		entry.Language = language.Normalize(atomEntry.Language)
 		if entry.Language == "" {
-			entry.Language = model.NormalizeLanguage(a.atomFeed.Language)
+			entry.Language = language.Normalize(a.atomFeed.Language)
 		}
 
 		// Populate the entry author.

+ 3 - 2
internal/reader/json/adapter.go

@@ -13,6 +13,7 @@ import (
 	"miniflux.app/v2/internal/crypto"
 	"miniflux.app/v2/internal/model"
 	"miniflux.app/v2/internal/reader/date"
+	"miniflux.app/v2/internal/reader/language"
 	"miniflux.app/v2/internal/reader/sanitizer"
 	"miniflux.app/v2/internal/urllib"
 )
@@ -31,7 +32,7 @@ func (j *JSONAdapter) BuildFeed(baseURL string) *model.Feed {
 		FeedURL:     strings.TrimSpace(j.jsonFeed.FeedURL),
 		SiteURL:     strings.TrimSpace(j.jsonFeed.HomePageURL),
 		Description: strings.TrimSpace(j.jsonFeed.Description),
-		Language:    model.NormalizeLanguage(j.jsonFeed.Language),
+		Language:    language.Normalize(j.jsonFeed.Language),
 	}
 
 	if feed.FeedURL == "" {
@@ -70,7 +71,7 @@ func (j *JSONAdapter) BuildFeed(baseURL string) *model.Feed {
 
 	for _, item := range j.jsonFeed.Items {
 		entry := model.NewEntry()
-		entry.Language = model.NormalizeLanguage(item.Language)
+		entry.Language = language.Normalize(item.Language)
 
 		for _, itemURL := range []string{item.URL, item.ExternalURL} {
 			if itemURL = strings.TrimSpace(itemURL); itemURL == "" {

+ 3 - 3
internal/model/language.go → internal/reader/language/language.go

@@ -1,17 +1,17 @@
 // SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
 // SPDX-License-Identifier: Apache-2.0
 
-package model // import "miniflux.app/v2/internal/model"
+package language // import "miniflux.app/v2/internal/reader/language"
 
 import "strings"
 
-// NormalizeLanguage cleans up a language tag declared by a feed so it is
+// Normalize cleans up a language tag declared by a feed so it is
 // suitable for use as an HTML lang attribute. It trims surrounding
 // whitespace, lower-cases the value, and replaces underscores with hyphens
 // (e.g. "en_US" -> "en-us"). No strict BCP-47 validation is performed:
 // many real feeds use loose values and silently dropping them yields worse
 // downstream behaviour than passing them through.
-func NormalizeLanguage(s string) string {
+func Normalize(s string) string {
 	s = strings.ToLower(strings.TrimSpace(s))
 	return strings.ReplaceAll(s, "_", "-")
 }

+ 4 - 4
internal/model/language_test.go → internal/reader/language/language_test.go

@@ -1,11 +1,11 @@
 // SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
 // SPDX-License-Identifier: Apache-2.0
 
-package model // import "miniflux.app/v2/internal/model"
+package language // import "miniflux.app/v2/internal/reader/language"
 
 import "testing"
 
-func TestNormalizeLanguage(t *testing.T) {
+func TestNormalize(t *testing.T) {
 	cases := []struct {
 		in, want string
 	}{
@@ -19,8 +19,8 @@ func TestNormalizeLanguage(t *testing.T) {
 		{"  fr-FR  ", "fr-fr"},
 	}
 	for _, c := range cases {
-		if got := NormalizeLanguage(c.in); got != c.want {
-			t.Errorf("NormalizeLanguage(%q) = %q, want %q", c.in, got, c.want)
+		if got := Normalize(c.in); got != c.want {
+			t.Errorf("Normalize(%q) = %q, want %q", c.in, got, c.want)
 		}
 	}
 }

+ 3 - 2
internal/reader/rdf/adapter.go

@@ -12,6 +12,7 @@ import (
 	"miniflux.app/v2/internal/crypto"
 	"miniflux.app/v2/internal/model"
 	"miniflux.app/v2/internal/reader/date"
+	"miniflux.app/v2/internal/reader/language"
 	"miniflux.app/v2/internal/reader/sanitizer"
 	"miniflux.app/v2/internal/urllib"
 )
@@ -26,7 +27,7 @@ func (r *rdfAdapter) buildFeed(baseURL string) *model.Feed {
 		FeedURL:     strings.TrimSpace(baseURL),
 		SiteURL:     strings.TrimSpace(r.rdf.Channel.Link),
 		Description: strings.TrimSpace(r.rdf.Channel.Description),
-		Language:    model.NormalizeLanguage(r.rdf.Channel.DublinCoreLanguage),
+		Language:    language.Normalize(r.rdf.Channel.DublinCoreLanguage),
 	}
 
 	if feed.Title == "" {
@@ -101,7 +102,7 @@ func (r *rdfAdapter) buildFeed(baseURL string) *model.Feed {
 			entry.Author = sanitizer.StripTags(r.rdf.Channel.DublinCoreCreator)
 		}
 
-		entry.Language = model.NormalizeLanguage(item.DublinCoreLanguage)
+		entry.Language = language.Normalize(item.DublinCoreLanguage)
 
 		feed.Entries = append(feed.Entries, entry)
 	}

+ 3 - 2
internal/reader/rss/adapter.go

@@ -17,6 +17,7 @@ import (
 	"miniflux.app/v2/internal/crypto"
 	"miniflux.app/v2/internal/model"
 	"miniflux.app/v2/internal/reader/date"
+	"miniflux.app/v2/internal/reader/language"
 	"miniflux.app/v2/internal/reader/sanitizer"
 	"miniflux.app/v2/internal/urllib"
 )
@@ -31,7 +32,7 @@ func (r *rssAdapter) buildFeed(baseURL string) *model.Feed {
 		FeedURL:     strings.TrimSpace(baseURL),
 		SiteURL:     strings.TrimSpace(r.rss.Channel.Link),
 		Description: strings.TrimSpace(r.rss.Channel.Description),
-		Language:    model.NormalizeLanguage(r.rss.Channel.Language),
+		Language:    language.Normalize(r.rss.Channel.Language),
 	}
 
 	// Ensure the Site URL is absolute.
@@ -114,7 +115,7 @@ func (r *rssAdapter) buildFeed(baseURL string) *model.Feed {
 			entry.Author = findFeedAuthor(&r.rss.Channel)
 		}
 
-		entry.Language = model.NormalizeLanguage(item.DublinCoreLanguage)
+		entry.Language = language.Normalize(item.DublinCoreLanguage)
 
 		// Generate the entry hash.
 		//