Commit 7bdc11dec1
Verified · cmc ci/build: success ci/lint: success ci/test: success
Layout: unified · split
app/cache.go +1 −1
| @@ -234,7 +234,7 @@ func (s skunkyart) DownloadAndSendMedia(subdomain, path string) { | |||
| 234 | default: | 234 | default: |
| 235 | s.Writer.Header().Del("Cache-Control") | 235 | s.Writer.Header().Del("Cache-Control") |
| 236 | s.Writer.WriteHeader(403) | 236 | s.Writer.WriteHeader(403) |
| 237 | response = []byte("Sorry, butt proxy on this instance are disabled.") | 237 | response = []byte(esc(T(s.Lang, "error.proxy"))) |
| 238 | } | 238 | } |
| 239 | 239 | ||
| 240 | _, _ = s.Writer.Write(response) | 240 | _, _ = s.Writer.Write(response) |
app/escape_test.go +3 −3
| @@ -43,9 +43,9 @@ func TestEveryPageTemplateRenders(t *testing.T) { | |||
| 43 | } | 43 | } |
| 44 | 44 | ||
| 45 | rec := httptest.NewRecorder() | 45 | rec := httptest.NewRecorder() |
| 46 | uri := "/" | 46 | s := skunkyart{Writer: rec, Host: "http://localhost", BasePath: "/", Lang: "en"} |
| 47 | skunkyart{Writer: rec}.ExecuteTemplate("index.htm", "html", &uri) | 47 | s.ExecuteTemplate("index.htm", "html", &s) |
| 48 | if rec.Code != 200 || !strings.Contains(rec.Body.String(), "</html>") { | 48 | if rec.Code != 200 || !strings.Contains(rec.Body.String(), "</html>") || !strings.Contains(rec.Body.String(), `lang="en"`) { |
| 49 | t.Errorf("index.htm: status %d, body %q", rec.Code, rec.Body.String()) | 49 | t.Errorf("index.htm: status %d, body %q", rec.Code, rec.Body.String()) |
| 50 | } | 50 | } |
| 51 | } | 51 | } |
app/i18n_test.go +47
| @@ -116,3 +116,50 @@ func TestResolveLangReadsAcceptLanguage(t *testing.T) { | |||
| 116 | t.Errorf("pinned-but-missing = %q, want %q", got, DefaultLang) | 116 | t.Errorf("pinned-but-missing = %q, want %q", got, DefaultLang) |
| 117 | } | 117 | } |
| 118 | } | 118 | } |
| 119 | |||
| 120 | // TestSpanishCatalogueMatchesEnglish keeps the two catalogues in step: a key | ||
| 121 | // added to one and not the other falls back to English silently, which is | ||
| 122 | // fine for a contributor's partial translation but not for the shipped one. | ||
| 123 | func TestSpanishCatalogueMatchesEnglish(t *testing.T) { | ||
| 124 | read := func(name string) map[string]string { | ||
| 125 | body, err := os.ReadFile("../static/lang/" + name) //nolint:gosec // G304: the test reads the repository's own catalogues | ||
| 126 | if err != nil { | ||
| 127 | t.Fatal(err) | ||
| 128 | } | ||
| 129 | var c map[string]string | ||
| 130 | if err := json.Unmarshal(body, &c); err != nil { | ||
| 131 | t.Fatal(err) | ||
| 132 | } | ||
| 133 | return c | ||
| 134 | } | ||
| 135 | en, es := read("en.json"), read("es.json") | ||
| 136 | for k := range en { | ||
| 137 | if _, ok := es[k]; !ok { | ||
| 138 | t.Errorf("es.json lacks %q", k) | ||
| 139 | } | ||
| 140 | } | ||
| 141 | for k := range es { | ||
| 142 | if _, ok := en[k]; !ok { | ||
| 143 | t.Errorf("es.json has %q, which en.json does not", k) | ||
| 144 | } | ||
| 145 | } | ||
| 146 | } | ||
| 147 | |||
| 148 | // TestGoBuiltStringsAreInTheCatalogue lists every key the Go builders ask for, | ||
| 149 | // so a typo in a T call shows up here rather than as the key on a page. | ||
| 150 | func TestGoBuiltStringsAreInTheCatalogue(t *testing.T) { | ||
| 151 | src, err := os.ReadFile("../static/lang/en.json") | ||
| 152 | if err != nil { | ||
| 153 | t.Fatal(err) | ||
| 154 | } | ||
| 155 | var en map[string]string | ||
| 156 | if err := json.Unmarshal(src, &en); err != nil { | ||
| 157 | t.Fatal(err) | ||
| 158 | } | ||
| 159 | for _, key := range []string{"comments.failed", "comments.reply_to", "list.text", "nav.prev", "nav.next", | ||
| 160 | "gallery.folders", "gallery.content", "error.nsfw", "error.proxy", "error.upstream", "deviation.comments", "deviation.open"} { | ||
| 161 | if _, ok := en[key]; !ok { | ||
| 162 | t.Errorf("en.json lacks %q, which Go code asks for", key) | ||
| 163 | } | ||
| 164 | } | ||
| 165 | } | ||
app/parsers.go +9 −5
| @@ -15,13 +15,15 @@ import ( | |||
| 15 | // fetch errored. | 15 | // fetch errored. |
| 16 | func (s skunkyart) ParseComments(c devianter.Comments, daError devianter.Error) string { | 16 | func (s skunkyart) ParseComments(c devianter.Comments, daError devianter.Error) string { |
| 17 | if daError.RAW != nil { | 17 | if daError.RAW != nil { |
| 18 | return "Failed to fetch comments :(" | 18 | return esc(T(s.Lang, "comments.failed")) |
| 19 | } | 19 | } |
| 20 | 20 | ||
| 21 | var cmmts strings.Builder | 21 | var cmmts strings.Builder |
| 22 | replied := make(map[int]string) | 22 | replied := make(map[int]string) |
| 23 | 23 | ||
| 24 | cmmts.WriteString("<details><summary>Comments: <b>") | 24 | cmmts.WriteString("<details><summary>") |
| 25 | cmmts.WriteString(esc(T(s.Lang, "deviation.comments"))) | ||
| 26 | cmmts.WriteString(": <b>") | ||
| 25 | cmmts.WriteString(strconv.Itoa(c.Total)) | 27 | cmmts.WriteString(strconv.Itoa(c.Total)) |
| 26 | cmmts.WriteString("</b></summary>") | 28 | cmmts.WriteString("</b></summary>") |
| 27 | for _, x := range c.Thread { | 29 | for _, x := range c.Thread { |
| @@ -49,7 +51,9 @@ func (s skunkyart) ParseComments(c devianter.Comments, daError devianter.Error) | |||
| 49 | cmmts.WriteString("</b></a> ") | 51 | cmmts.WriteString("</b></a> ") |
| 50 | 52 | ||
| 51 | if x.Parent > 0 { | 53 | if x.Parent > 0 { |
| 52 | cmmts.WriteString(` In reply to <a href="`) | 54 | cmmts.WriteString(" ") |
| 55 | cmmts.WriteString(esc(T(s.Lang, "comments.reply_to"))) | ||
| 56 | cmmts.WriteString(` <a href="`) | ||
| 53 | cmmts.WriteString(esc(s._pth)) | 57 | cmmts.WriteString(esc(s._pth)) |
| 54 | cmmts.WriteString("#") | 58 | cmmts.WriteString("#") |
| 55 | cmmts.WriteString(strconv.Itoa(x.Parent)) | 59 | cmmts.WriteString(strconv.Itoa(x.Parent)) |
| @@ -152,13 +156,13 @@ func (s skunkyart) DeviationList(devs []devianter.Deviation, allowAtom bool, con | |||
| 152 | } else { | 156 | } else { |
| 153 | listContent.WriteString(`<div class="block">`) | 157 | listContent.WriteString(`<div class="block">`) |
| 154 | if fullview != "" && preview != "" { | 158 | if fullview != "" && preview != "" { |
| 155 | listContent.WriteString(`<a title="open/download" href="`) | 159 | listContent.WriteString(`<a title="` + esc(T(s.Lang, "deviation.open")) + `" href="`) |
| 156 | listContent.WriteString(fullview) | 160 | listContent.WriteString(fullview) |
| 157 | listContent.WriteString(`"><img loading="lazy" src="`) | 161 | listContent.WriteString(`"><img loading="lazy" src="`) |
| 158 | listContent.WriteString(preview) | 162 | listContent.WriteString(preview) |
| 159 | listContent.WriteString(`" width="15%"></a>`) | 163 | listContent.WriteString(`" width="15%"></a>`) |
| 160 | } else { | 164 | } else { |
| 161 | listContent.WriteString(`<h1>[ TEXT ]</h1>`) | 165 | listContent.WriteString("<h1>[ " + esc(T(s.Lang, "list.text")) + " ]</h1>") |
| 162 | } | 166 | } |
| 163 | listContent.WriteString(`<br><a href="`) | 167 | listContent.WriteString(`<br><a href="`) |
| 164 | listContent.WriteString(postURL) | 168 | listContent.WriteString(postURL) |
app/router.go +1 −1
| @@ -146,7 +146,7 @@ func Handler() http.HandlerFunc { | |||
| 146 | switch skunky.Endpoint { | 146 | switch skunky.Endpoint { |
| 147 | // main | 147 | // main |
| 148 | case "": | 148 | case "": |
| 149 | skunky.ExecuteTemplate("index.htm", "html", &CFG.URI) | 149 | skunky.ExecuteTemplate("index.htm", "html", &skunky) |
| 150 | case "about": | 150 | case "about": |
| 151 | skunky.Templates.About = About | 151 | skunky.Templates.About = About |
| 152 | skunky.ExecuteTemplate("about.htm", "html", &skunky) | 152 | skunky.ExecuteTemplate("about.htm", "html", &skunky) |
app/util.go +3 −3
| @@ -233,7 +233,7 @@ func (s skunkyart) Error(dAerr devianter.Error) { | |||
| 233 | var msg strings.Builder | 233 | var msg strings.Builder |
| 234 | msg.WriteString(`<html><link rel="stylesheet" href="`) | 234 | msg.WriteString(`<html><link rel="stylesheet" href="`) |
| 235 | msg.WriteString(URLBuilder(s.Host, "stylesheet")) | 235 | msg.WriteString(URLBuilder(s.Host, "stylesheet")) |
| 236 | msg.WriteString(`" /><h3>DeviantArt error — '`) | 236 | msg.WriteString(`" /><h3>` + esc(T(s.Lang, "error.upstream")) + ` — '`) |
| 237 | msg.WriteString(esc(reason)) | 237 | msg.WriteString(esc(reason)) |
| 238 | msg.WriteString("'</h3></html>") | 238 | msg.WriteString("'</h3></html>") |
| 239 | 239 | ||
| @@ -430,7 +430,7 @@ func (s skunkyart) NavBase(c DeviationList) string { | |||
| 430 | p := s.Page | 430 | p := s.Page |
| 431 | 431 | ||
| 432 | if p > 1 { | 432 | if p > 1 { |
| 433 | prevrev("<= Prev |", p-1, false) | 433 | prevrev("<= "+esc(T(s.Lang, "nav.prev"))+" |", p-1, false) |
| 434 | } else { | 434 | } else { |
| 435 | p = 1 | 435 | p = 1 |
| 436 | } | 436 | } |
| @@ -456,7 +456,7 @@ func (s skunkyart) NavBase(c DeviationList) string { | |||
| 456 | } | 456 | } |
| 457 | 457 | ||
| 458 | if c.More { | 458 | if c.More { |
| 459 | prevrev("| Next =>", p+1, false) | 459 | prevrev("| "+esc(T(s.Lang, "nav.next"))+" =>", p+1, false) |
| 460 | } | 460 | } |
| 461 | 461 | ||
| 462 | return list.String() | 462 | return list.String() |
app/wrapper.go +3 −3
| @@ -172,7 +172,7 @@ func (s skunkyart) GRUser() { | |||
| 172 | for _, x := range content.Content.Gruser.Page.Modules { | 172 | for _, x := range content.Content.Gruser.Page.Modules { |
| 173 | if len(x.ModuleData.Folders.Results) != 0 { | 173 | if len(x.ModuleData.Folders.Results) != 0 { |
| 174 | var folders strings.Builder | 174 | var folders strings.Builder |
| 175 | folders.WriteString(`<h1 id="folders"><a href="#folder">#</a> Folders</h1><div class="folders"><br>`) | 175 | folders.WriteString(`<h1 id="folders"><a href="#folders">#</a> ` + esc(T(s.Lang, "gallery.folders")) + `</h1><div class="folders"><br>`) |
| 176 | for _, x := range x.ModuleData.Folders.Results { | 176 | for _, x := range x.ModuleData.Folders.Results { |
| 177 | if x.FolderId != -1 && x.Size != 0 { | 177 | if x.FolderId != -1 && x.Size != 0 { |
| 178 | folders.WriteString(`<div class="block folder-item">`) | 178 | folders.WriteString(`<div class="block folder-item">`) |
| @@ -203,7 +203,7 @@ func (s skunkyart) GRUser() { | |||
| 203 | folders.WriteString("</div>") | 203 | folders.WriteString("</div>") |
| 204 | } | 204 | } |
| 205 | } | 205 | } |
| 206 | folders.WriteString(`</div><h1 id="content"><a href="#content">#</a> Content</h1>`) | 206 | folders.WriteString(`</div><h1 id="content"><a href="#content">#</a> ` + esc(T(s.Lang, "gallery.content")) + `</h1>`) |
| 207 | group.Gallery.Folders = template.HTML(folders.String()) //nolint:gosec // G203: escaped above | 207 | group.Gallery.Folders = template.HTML(folders.String()) //nolint:gosec // G203: escaped above |
| 208 | } | 208 | } |
| 209 | 209 | ||
| @@ -248,7 +248,7 @@ func (s skunkyart) Deviation(author, postname string) { | |||
| 248 | s.Writer.WriteHeader(403) | 248 | s.Writer.WriteHeader(403) |
| 249 | wr(s.Writer, `<html><link rel="stylesheet" href="`+ | 249 | wr(s.Writer, `<html><link rel="stylesheet" href="`+ |
| 250 | URLBuilder(s.Host, "stylesheet")+ | 250 | URLBuilder(s.Host, "stylesheet")+ |
| 251 | `" /><h1>NSFW content are disabled on this instance.</h1></html>`) | 251 | `" /><h1>`+esc(T(s.Lang, "error.nsfw"))+`</h1></html>`) |
| 252 | return | 252 | return |
| 253 | } | 253 | } |
| 254 | 254 | ||
static/html/about.htm +2 −2
| @@ -1,10 +1,10 @@ | |||
| 1 | <!DOCTYPE html> | 1 | <!DOCTYPE html> |
| 2 | <html lang="en"> | 2 | <html lang="{{.Lang}}"> |
| 3 | {{template "head" .}} | 3 | {{template "head" .}} |
| 4 | <main> | 4 | <main> |
| 5 | {{template "header" .}} | 5 | {{template "header" .}} |
| 6 | <p> | 6 | <p> |
| 7 | SkunkyArt is an alternative frontend for deviantart.com, written in Go. | 7 | {{T "about.blurb"}} |
| 8 | </p> | 8 | </p> |
| 9 | <h3><a href="https://github.com/krazywarez/skunky-art/issues" target="_blank">{{T "about.report"}}</a></h3> | 9 | <h3><a href="https://github.com/krazywarez/skunky-art/issues" target="_blank">{{T "about.report"}}</a></h3> |
| 10 | <b>{{T "about.settings"}}</b> | 10 | <b>{{T "about.settings"}}</b> |
static/html/daily.htm +3 −3
| @@ -1,13 +1,13 @@ | |||
| 1 | <!DOCTYPE html> | 1 | <!DOCTYPE html> |
| 2 | <html lang="en"> | 2 | <html lang="{{.Lang}}"> |
| 3 | {{template "head" . }} | 3 | {{template "head" . }} |
| 4 | <main> | 4 | <main> |
| 5 | {{template "header" . }} | 5 | {{template "header" . }} |
| 6 | {{if ne .Templates.DDStrips ""}} | 6 | {{if ne .Templates.DDStrips ""}} |
| 7 | <h2 id="strips"><a href="#strips">#</a> Strips</h2> | 7 | <h2 id="strips"><a href="#strips">#</a> {{T "daily.strips"}}</h2> |
| 8 | {{.Templates.DDStrips}} | 8 | {{.Templates.DDStrips}} |
| 9 | {{end}} | 9 | {{end}} |
| 10 | <h2 id="content"><a href="#content">#</a> Content</h2> | 10 | <h2 id="content"><a href="#content">#</a> {{T "gallery.content"}}</h2> |
| 11 | {{.Templates.SomeList}} | 11 | {{.Templates.SomeList}} |
| 12 | </main> | 12 | </main> |
| 13 | </html>\ No newline at end of file | 13 | </html>\ No newline at end of file |
static/html/deviantion.htm +2 −2
| @@ -1,5 +1,5 @@ | |||
| 1 | <!DOCTYPE html> | 1 | <!DOCTYPE html> |
| 2 | <html lang="en"> | 2 | <html lang="{{.Lang}}"> |
| 3 | {{template "head" . }} | 3 | {{template "head" . }} |
| 4 | <main> | 4 | <main> |
| 5 | {{template "header" . }} | 5 | {{template "header" . }} |
| @@ -19,7 +19,7 @@ | |||
| 19 | {{if (ne .Templates.Deviation.Tags "")}} | 19 | {{if (ne .Templates.Deviation.Tags "")}} |
| 20 | {{.Templates.Deviation.Tags}}<br> | 20 | {{.Templates.Deviation.Tags}}<br> |
| 21 | {{end}} | 21 | {{end}} |
| 22 | <span>{{T "deviation.published"}}<strong>{{.Templates.Deviation.StringTime}}</strong>; Views: <strong>{{.Templates.Deviation.Post.Deviation.Stats.Views}}</strong>; Favourites: <strong>{{.Templates.Deviation.Post.Deviation.Stats.Favourites}}</strong>; Downloads: <strong>{{.Templates.Deviation.Post.Deviation.Stats.Downloads}}</strong> | 22 | <span>{{T "deviation.published"}}<strong>{{.Templates.Deviation.StringTime}}</strong>; {{T "deviation.views"}}: <strong>{{.Templates.Deviation.Post.Deviation.Stats.Views}}</strong>; {{T "nav.favourites"}}: <strong>{{.Templates.Deviation.Post.Deviation.Stats.Favourites}}</strong>; {{T "deviation.downloads"}}: <strong>{{.Templates.Deviation.Post.Deviation.Stats.Downloads}}</strong> |
| 23 | <br><a target="_blank" href="https://www.deviantart.com/{{.Templates.Deviation.Post.Deviation.Author.Username}}/art/art-{{.Templates.Deviation.Post.Deviation.ID}}">{{T "deviation.original"}}</a> | 23 | <br><a target="_blank" href="https://www.deviantart.com/{{.Templates.Deviation.Post.Deviation.Author.Username}}/art/art-{{.Templates.Deviation.Post.Deviation.ID}}">{{T "deviation.original"}}</a> |
| 24 | </span> | 24 | </span> |
| 25 | {{if (ne .Templates.Deviation.Description "")}} | 25 | {{if (ne .Templates.Deviation.Description "")}} |
static/html/gruser.htm +6 −6
| @@ -1,5 +1,5 @@ | |||
| 1 | <!DOCTYPE html> | 1 | <!DOCTYPE html> |
| 2 | <html lang="en"> | 2 | <html lang="{{.Lang}}"> |
| 3 | {{template "head" . }} | 3 | {{template "head" . }} |
| 4 | <main> | 4 | <main> |
| 5 | <header> | 5 | <header> |
| @@ -59,29 +59,29 @@ | |||
| 59 | {{end}} | 59 | {{end}} |
| 60 | 60 | ||
| 61 | {{if ne .Templates.GroupUser.Admins ""}} | 61 | {{if ne .Templates.GroupUser.Admins ""}} |
| 62 | <h3 id="admins"><a href="#admins">#</a> Group admins</h3> | 62 | <h3 id="admins"><a href="#admins">#</a> {{T "user.admins"}}</h3> |
| 63 | <div class="plates admins"> | 63 | <div class="plates admins"> |
| 64 | {{.Templates.GroupUser.Admins}} | 64 | {{.Templates.GroupUser.Admins}} |
| 65 | </div> | 65 | </div> |
| 66 | {{end}} | 66 | {{end}} |
| 67 | 67 | ||
| 68 | {{if ne .Templates.GroupUser.About.Interests ""}} | 68 | {{if ne .Templates.GroupUser.About.Interests ""}} |
| 69 | <h3 id="interests"><a href="#interests">#</a> Interests</h3> | 69 | <h3 id="interests"><a href="#interests">#</a> {{T "user.interests"}}</h3> |
| 70 | {{.Templates.GroupUser.About.Interests}} | 70 | {{.Templates.GroupUser.About.Interests}} |
| 71 | {{end}} | 71 | {{end}} |
| 72 | 72 | ||
| 73 | {{if ne .Templates.GroupUser.About.Social ""}} | 73 | {{if ne .Templates.GroupUser.About.Social ""}} |
| 74 | <h3 id="social"><a href="#social">#</a> Social Links</h3> | 74 | <h3 id="social"><a href="#social">#</a> {{T "user.social"}}</h3> |
| 75 | {{.Templates.GroupUser.About.Social}} | 75 | {{.Templates.GroupUser.About.Social}} |
| 76 | {{end}} | 76 | {{end}} |
| 77 | 77 | ||
| 78 | {{if ne .Templates.GroupUser.About.DescriptionFormatted ""}} | 78 | {{if ne .Templates.GroupUser.About.DescriptionFormatted ""}} |
| 79 | <h3 id="about"><a href="#about">#</a> About me</h3> | 79 | <h3 id="about"><a href="#about">#</a> {{T "user.about"}}</h3> |
| 80 | {{.Templates.GroupUser.About.DescriptionFormatted}} | 80 | {{.Templates.GroupUser.About.DescriptionFormatted}} |
| 81 | {{end}} | 81 | {{end}} |
| 82 | {{if ne .Templates.GroupUser.About.Comments ""}} | 82 | {{if ne .Templates.GroupUser.About.Comments ""}} |
| 83 | <br> | 83 | <br> |
| 84 | <h3 id="comments"><a href="#comments">#</a> Comments</h3> | 84 | <h3 id="comments"><a href="#comments">#</a> {{T "deviation.comments"}}</h3> |
| 85 | {{.Templates.GroupUser.About.Comments}} | 85 | {{.Templates.GroupUser.About.Comments}} |
| 86 | {{end}} | 86 | {{end}} |
| 87 | {{else}} | 87 | {{else}} |
static/html/index.htm +6 −6
| @@ -1,9 +1,9 @@ | |||
| 1 | <!DOCTYPE html> | 1 | <!DOCTYPE html> |
| 2 | <html lang="en"> | 2 | <html lang="{{.Lang}}"> |
| 3 | <head> | 3 | <head> |
| 4 | <title>SkunkyArt</title> | 4 | <title>SkunkyArt</title> |
| 5 | <link rel="stylesheet" href="{{.}}stylesheet"/> | 5 | <link rel="stylesheet" href="{{.BasePath}}stylesheet"/> |
| 6 | <link rel="icon" type="image/x-icon" href="{{.}}favicon.ico"> | 6 | <link rel="icon" type="image/x-icon" href="{{.BasePath}}favicon.ico"> |
| 7 | <meta name="viewport" content="width=device-width, height=device-height, initial-scale=0.4"/> | 7 | <meta name="viewport" content="width=device-width, height=device-height, initial-scale=0.4"/> |
| 8 | 8 | ||
| 9 | <style> | 9 | <style> |
| @@ -70,10 +70,10 @@ | |||
| 70 | </style> | 70 | </style> |
| 71 | </head> | 71 | </head> |
| 72 | <main> | 72 | <main> |
| 73 | <img src="{{.}}favicon.ico" title="{{T "index.logo"}}" alt="SkunkyArt logo" draggable="false"> | 73 | <img src="{{.BasePath}}favicon.ico" title="{{T "index.logo"}}" alt="SkunkyArt logo" draggable="false"> |
| 74 | <div> | 74 | <div> |
| 75 | <h1><a href="{{.}}dd">{{T "nav.daily"}}</a> | <a href="{{.}}about">{{T "nav.about"}}</a></h1> | 75 | <h1><a href="{{.BasePath}}dd">{{T "nav.daily"}}</a> | <a href="{{.BasePath}}about">{{T "nav.about"}}</a></h1> |
| 76 | <form method="get" action="{{.}}search"> | 76 | <form method="get" action="{{.BasePath}}search"> |
| 77 | <input type="text" name="q" aria-label="Search query" placeholder="{{T "search.placeholder"}}" autocomplete="off" autocapitalize="none" spellcheck="false"> | 77 | <input type="text" name="q" aria-label="Search query" placeholder="{{T "search.placeholder"}}" autocomplete="off" autocapitalize="none" spellcheck="false"> |
| 78 | <select name="type" aria-label="Search type"> | 78 | <select name="type" aria-label="Search type"> |
| 79 | <option value="all">{{T "search.all"}}</option> | 79 | <option value="all">{{T "search.all"}}</option> |
static/html/search.htm +3 −3
| @@ -1,16 +1,16 @@ | |||
| 1 | <!DOCTYPE html> | 1 | <!DOCTYPE html> |
| 2 | <html lang="en"> | 2 | <html lang="{{.Lang}}"> |
| 3 | {{template "head" . }} | 3 | {{template "head" . }} |
| 4 | <main> | 4 | <main> |
| 5 | {{template "header" . }} | 5 | {{template "header" . }} |
| 6 | 6 | ||
| 7 | {{if ne .Templates.Search.List ""}} | 7 | {{if ne .Templates.Search.List ""}} |
| 8 | {{if ne .Templates.Search.Content.Total 0}} | 8 | {{if ne .Templates.Search.Content.Total 0}} |
| 9 | <h1>Results by request '{{.QueryRaw}}': {{.Templates.Search.Content.Total}}</h1> | 9 | <h1>{{T "search.results"}} '{{.QueryRaw}}': {{.Templates.Search.Content.Total}}</h1> |
| 10 | {{end}} | 10 | {{end}} |
| 11 | {{.Templates.Search.List}} | 11 | {{.Templates.Search.List}} |
| 12 | {{else}} | 12 | {{else}} |
| 13 | <p>No results :(</p> | 13 | <p>{{T "search.none"}}</p> |
| 14 | {{end}} | 14 | {{end}} |
| 15 | </main> | 15 | </main> |
| 16 | </html>\ No newline at end of file | 16 | </html>\ No newline at end of file |
static/lang/en.json +21 −1
| @@ -43,5 +43,25 @@ | |||
| 43 | "user.friends": "Friends", | 43 | "user.friends": "Friends", |
| 44 | "nav.daily": "Daily Deviations", | 44 | "nav.daily": "Daily Deviations", |
| 45 | "index.logo": "SkunkyArt logo", | 45 | "index.logo": "SkunkyArt logo", |
| 46 | "index.source": "Source Code" | 46 | "index.source": "Source Code", |
| 47 | "comments.failed": "Failed to fetch comments :(", | ||
| 48 | "comments.reply_to": "In reply to", | ||
| 49 | "list.text": "TEXT", | ||
| 50 | "nav.prev": "Prev", | ||
| 51 | "nav.next": "Next", | ||
| 52 | "gallery.folders": "Folders", | ||
| 53 | "gallery.content": "Content", | ||
| 54 | "daily.strips": "Strips", | ||
| 55 | "search.results": "Results for", | ||
| 56 | "search.none": "No results :(", | ||
| 57 | "user.admins": "Group admins", | ||
| 58 | "user.interests": "Interests", | ||
| 59 | "user.social": "Social links", | ||
| 60 | "user.about": "About me", | ||
| 61 | "deviation.views": "Views", | ||
| 62 | "deviation.downloads": "Downloads", | ||
| 63 | "about.blurb": "SkunkyArt is an alternative frontend for deviantart.com, written in Go.", | ||
| 64 | "error.nsfw": "NSFW content is disabled on this instance.", | ||
| 65 | "error.proxy": "Media proxying is disabled on this instance.", | ||
| 66 | "error.upstream": "DeviantArt error" | ||
| 47 | } | 67 | } |
static/lang/es.json +21 −1
| @@ -1,4 +1,5 @@ | |||
| 1 | { | 1 | { |
| 2 | "about.blurb": "SkunkyArt es una interfaz alternativa para deviantart.com, escrita en Go.", | ||
| 2 | "about.copyright": "Copyright ", | 3 | "about.copyright": "Copyright ", |
| 3 | "about.country": "País", | 4 | "about.country": "País", |
| 4 | "about.hideai": "Ocultar IA", | 5 | "about.hideai": "Ocultar IA", |
| @@ -10,35 +11,54 @@ | |||
| 10 | "about.theme": "Tema", | 11 | "about.theme": "Tema", |
| 11 | "about.urls": "URLs", | 12 | "about.urls": "URLs", |
| 12 | "about.yes": "Sí", | 13 | "about.yes": "Sí", |
| 14 | "comments.failed": "No se pudieron cargar los comentarios :(", | ||
| 15 | "comments.reply_to": "En respuesta a", | ||
| 13 | "common.settings": "Ajustes", | 16 | "common.settings": "Ajustes", |
| 17 | "daily.strips": "Tiras", | ||
| 18 | "deviation.comments": "Comentarios", | ||
| 14 | "deviation.daily": "¡Desviación del día!", | 19 | "deviation.daily": "¡Desviación del día!", |
| 15 | "deviation.description": "Descripción", | 20 | "deviation.description": "Descripción", |
| 21 | "deviation.downloads": "Descargas", | ||
| 16 | "deviation.license": "Licencia", | 22 | "deviation.license": "Licencia", |
| 17 | "deviation.nsfw": "NSFW", | 23 | "deviation.nsfw": "NSFW", |
| 18 | "deviation.open": "abrir/descargar imagen", | 24 | "deviation.open": "abrir/descargar imagen", |
| 19 | "deviation.original": "Ir al original", | 25 | "deviation.original": "Ir al original", |
| 20 | "deviation.published": "Publicado: ", | 26 | "deviation.published": "Publicado: ", |
| 21 | "deviation.related": "Contenido relacionado", | 27 | "deviation.related": "Contenido relacionado", |
| 22 | "deviation.comments": "Comentarios", | 28 | "deviation.views": "Vistas", |
| 29 | "error.nsfw": "El contenido NSFW está desactivado en esta instancia.", | ||
| 30 | "error.proxy": "El proxy de medios está desactivado en esta instancia.", | ||
| 31 | "error.upstream": "Error de DeviantArt", | ||
| 32 | "gallery.content": "Contenido", | ||
| 33 | "gallery.folders": "Carpetas", | ||
| 23 | "index.logo": "Logo de SkunkyArt", | 34 | "index.logo": "Logo de SkunkyArt", |
| 24 | "index.source": "Código fuente", | 35 | "index.source": "Código fuente", |
| 36 | "list.text": "TEXTO", | ||
| 25 | "nav.about": "Acerca de", | 37 | "nav.about": "Acerca de", |
| 26 | "nav.daily": "Desviaciones del día", | 38 | "nav.daily": "Desviaciones del día", |
| 27 | "nav.dd": "DD", | 39 | "nav.dd": "DD", |
| 28 | "nav.favourites": "Favoritos", | 40 | "nav.favourites": "Favoritos", |
| 29 | "nav.gallery": "Galería", | 41 | "nav.gallery": "Galería", |
| 30 | "nav.home": "INICIO", | 42 | "nav.home": "INICIO", |
| 43 | "nav.next": "Siguiente", | ||
| 44 | "nav.prev": "Anterior", | ||
| 31 | "nav.rss": "RSS", | 45 | "nav.rss": "RSS", |
| 32 | "search.all": "Todo", | 46 | "search.all": "Todo", |
| 33 | "search.groups": "Grupos", | 47 | "search.groups": "Grupos", |
| 48 | "search.none": "Sin resultados :(", | ||
| 34 | "search.placeholder": "Buscar ...", | 49 | "search.placeholder": "Buscar ...", |
| 50 | "search.results": "Resultados para", | ||
| 35 | "search.submit": "¡Buscar!", | 51 | "search.submit": "¡Buscar!", |
| 36 | "search.tag": "Etiqueta", | 52 | "search.tag": "Etiqueta", |
| 53 | "user.about": "Sobre mí", | ||
| 54 | "user.admins": "Administradores del grupo", | ||
| 37 | "user.comments": "Comentarios", | 55 | "user.comments": "Comentarios", |
| 38 | "user.deviations": "Desviaciones", | 56 | "user.deviations": "Desviaciones", |
| 39 | "user.friends": "Amigos", | 57 | "user.friends": "Amigos", |
| 58 | "user.interests": "Intereses", | ||
| 40 | "user.pageviews": "Visitas", | 59 | "user.pageviews": "Visitas", |
| 41 | "user.registered": "Fecha de registro", | 60 | "user.registered": "Fecha de registro", |
| 61 | "user.social": "Enlaces sociales", | ||
| 42 | "user.statistics": "Estadísticas", | 62 | "user.statistics": "Estadísticas", |
| 43 | "user.tag": "Etiqueta del usuario", | 63 | "user.tag": "Etiqueta del usuario", |
| 44 | "user.uid": "UID", | 64 | "user.uid": "UID", |