feat: template render time i18n

This commit is contained in:
kumfo 2022-12-05 11:27:37 +08:00
parent 999ee83c8d
commit 19f119423f
11 changed files with 170 additions and 63 deletions

View File

@ -5889,6 +5889,7 @@ const docTemplate = `{
"contact_email", "contact_email",
"description", "description",
"name", "name",
"permalink",
"short_description", "short_description",
"site_url" "site_url"
], ],
@ -5905,6 +5906,11 @@ const docTemplate = `{
"type": "string", "type": "string",
"maxLength": 128 "maxLength": 128
}, },
"permalink": {
"type": "integer",
"maximum": 3,
"minimum": 0
},
"short_description": { "short_description": {
"type": "string", "type": "string",
"maxLength": 255 "maxLength": 255
@ -5921,6 +5927,7 @@ const docTemplate = `{
"contact_email", "contact_email",
"description", "description",
"name", "name",
"permalink",
"short_description", "short_description",
"site_url" "site_url"
], ],
@ -5937,6 +5944,11 @@ const docTemplate = `{
"type": "string", "type": "string",
"maxLength": 128 "maxLength": 128
}, },
"permalink": {
"type": "integer",
"maximum": 3,
"minimum": 0
},
"short_description": { "short_description": {
"type": "string", "type": "string",
"maxLength": 255 "maxLength": 255

View File

@ -5877,6 +5877,7 @@
"contact_email", "contact_email",
"description", "description",
"name", "name",
"permalink",
"short_description", "short_description",
"site_url" "site_url"
], ],
@ -5893,6 +5894,11 @@
"type": "string", "type": "string",
"maxLength": 128 "maxLength": 128
}, },
"permalink": {
"type": "integer",
"maximum": 3,
"minimum": 0
},
"short_description": { "short_description": {
"type": "string", "type": "string",
"maxLength": 255 "maxLength": 255
@ -5909,6 +5915,7 @@
"contact_email", "contact_email",
"description", "description",
"name", "name",
"permalink",
"short_description", "short_description",
"site_url" "site_url"
], ],
@ -5925,6 +5932,11 @@
"type": "string", "type": "string",
"maxLength": 128 "maxLength": 128
}, },
"permalink": {
"type": "integer",
"maximum": 3,
"minimum": 0
},
"short_description": { "short_description": {
"type": "string", "type": "string",
"maxLength": 255 "maxLength": 255

View File

@ -1122,6 +1122,10 @@ definitions:
name: name:
maxLength: 128 maxLength: 128
type: string type: string
permalink:
maximum: 3
minimum: 0
type: integer
short_description: short_description:
maxLength: 255 maxLength: 255
type: string type: string
@ -1132,6 +1136,7 @@ definitions:
- contact_email - contact_email
- description - description
- name - name
- permalink
- short_description - short_description
- site_url - site_url
type: object type: object
@ -1146,6 +1151,10 @@ definitions:
name: name:
maxLength: 128 maxLength: 128
type: string type: string
permalink:
maximum: 3
minimum: 0
type: integer
short_description: short_description:
maxLength: 255 maxLength: 255
type: string type: string
@ -1156,6 +1165,7 @@ definitions:
- contact_email - contact_email
- description - description
- name - name
- permalink
- short_description - short_description
- site_url - site_url
type: object type: object

View File

@ -187,6 +187,17 @@ backend:
other: "Your answer has been deleted" other: "Your answer has been deleted"
your_comment_was_deleted: your_comment_was_deleted:
other: "Your comment has been deleted" other: "Your comment has been deleted"
dates:
long_date: "Jan 2"
long_date_with_year: "Jan 2, 2006"
long_date_with_time: "Jan 2, 2006 at 15:04"
now: now
x_seconds_ago: "{{count}}s ago"
x_minutes_ago: "{{count}}m ago"
x_hours_ago: "{{count}}h ago"
hour: hour
day: day
# The following fields are used for interface presentation(Front-end) # The following fields are used for interface presentation(Front-end)
ui: ui:
how_to_format: how_to_format:

View File

@ -171,6 +171,14 @@ backend:
other: "你的答案已被删除" other: "你的答案已被删除"
your_comment_was_deleted: your_comment_was_deleted:
other: "你的评论已被删除" other: "你的评论已被删除"
dates:
long_date: "01月02日"
long_date_with_year: "2006年01月02日"
long_date_with_time: "2006年01月02日 15:04"
now: 刚刚
x_seconds_ago: '{{count}} 秒前'
x_minutes_ago: '{{count}} 分钟前'
x_hours_ago: '{{count}} 小时前'
# The following fields are used for interface presentation(Front-end) # The following fields are used for interface presentation(Front-end)
ui: ui:
how_to_format: how_to_format:

View File

@ -1,9 +1,14 @@
package server package server
import ( import (
"github.com/answerdev/answer/internal/schema"
"github.com/answerdev/answer/pkg/converter"
"html/template" "html/template"
"io/fs" "io/fs"
"math"
"os" "os"
"strconv"
"strings"
"time" "time"
brotli "github.com/anargu/gin-brotli" brotli "github.com/anargu/gin-brotli"
@ -64,8 +69,62 @@ func NewHTTPServer(debug bool,
"translator": func(la i18n.Language, data string) string { "translator": func(la i18n.Language, data string) string {
return translator.GlobalTrans.Tr(la, data) return translator.GlobalTrans.Tr(la, data)
}, },
"translatorTimeFormat": func(la i18n.Language, timestamp int64) string { "timeFormatISO": func(tz string, timestamp int64) string {
return time.Unix(timestamp, 0).Format("2006-01-02 15:04:05") _, _ = time.LoadLocation(tz)
return time.Unix(timestamp, 0).Format("2006-01-02T15:04:05.000Z")
},
"translatorTimeFormatLongDate": func(la i18n.Language, tz string, timestamp int64) string {
trans := translator.GlobalTrans.Tr(la, "dates.long_date_with_time")
return time.Unix(timestamp, 0).Format(trans)
},
"translatorTimeFormat": func(la i18n.Language, tz string, timestamp int64) string {
var (
now = time.Now().Unix()
between int64 = 0
trans string
)
_, _ = time.LoadLocation(tz)
if now > timestamp {
between = now - timestamp
}
if between <= 1 {
return translator.GlobalTrans.Tr(la, "dates.now")
}
if between > 1 && between < 60 {
trans = translator.GlobalTrans.Tr(la, "dates.x_seconds_ago")
return strings.ReplaceAll(trans, "{{count}}", converter.IntToString(between))
}
if between >= 60 && between < 3600 {
min := math.Floor(float64(between / 60))
trans = translator.GlobalTrans.Tr(la, "dates.x_minutes_ago")
return strings.ReplaceAll(trans, "{{count}}", strconv.FormatFloat(min, 'f', 0, 64))
}
if between >= 3600 && between < 3600*24 {
h := math.Floor(float64(between / 60))
trans = translator.GlobalTrans.Tr(la, "dates.x_hours_ago")
return strings.ReplaceAll(trans, "{{count}}", strconv.FormatFloat(h, 'f', 0, 64))
}
if between >= 3600*24 &&
between < 3600*24*366 &&
time.Unix(timestamp, 0).Format("2006") == time.Unix(now, 0).Format("2006") {
trans = translator.GlobalTrans.Tr(la, "dates.long_date")
return time.Unix(timestamp, 0).Format(trans)
}
trans = translator.GlobalTrans.Tr(la, "dates.long_date_with_year")
return time.Unix(timestamp, 0).Format(trans)
},
"wrapComments": func(comments []*schema.GetCommentResp, la i18n.Language, tz string) map[string]interface{} {
return map[string]interface{}{
"comments": comments,
"language": la,
"timezone": tz,
}
}, },
}) })

View File

@ -90,12 +90,10 @@ func (tc *TemplateController) Index(ctx *gin.Context) {
tc.Page404(ctx) tc.Page404(ctx)
return return
} }
siteInfo := tc.SiteInfo(ctx) siteInfo := tc.SiteInfo(ctx)
siteInfo.Canonical = fmt.Sprintf("%s", siteInfo.General.SiteUrl) siteInfo.Canonical = fmt.Sprintf("%s", siteInfo.General.SiteUrl)
ctx.HTML(http.StatusOK, "question.html", gin.H{ tc.html(ctx, http.StatusOK, "question.html", siteInfo, gin.H{
"siteinfo": siteInfo,
"scriptPath": tc.scriptPath,
"cssPath": tc.cssPath,
"data": data, "data": data,
"page": templaterender.Paginator(page, req.PageSize, count), "page": templaterender.Paginator(page, req.PageSize, count),
}) })
@ -115,10 +113,8 @@ func (tc *TemplateController) QuestionList(ctx *gin.Context) {
} }
siteInfo := tc.SiteInfo(ctx) siteInfo := tc.SiteInfo(ctx)
siteInfo.Canonical = fmt.Sprintf("%s/questions", siteInfo.General.SiteUrl) siteInfo.Canonical = fmt.Sprintf("%s/questions", siteInfo.General.SiteUrl)
ctx.HTML(http.StatusOK, "question.html", gin.H{
"siteinfo": siteInfo, tc.html(ctx, http.StatusOK, "question.html", siteInfo, gin.H{
"scriptPath": tc.scriptPath,
"cssPath": tc.cssPath,
"data": data, "data": data,
"page": templaterender.Paginator(page, req.PageSize, count), "page": templaterender.Paginator(page, req.PageSize, count),
}) })
@ -194,15 +190,12 @@ func (tc *TemplateController) QuestionInfo(ctx *gin.Context) {
siteInfo.JsonLD = `<script data-react-helmet="true" type="application/ld+json">` + string(jsonLDStr) + ` </script>` siteInfo.JsonLD = `<script data-react-helmet="true" type="application/ld+json">` + string(jsonLDStr) + ` </script>`
} }
ctx.HTML(http.StatusOK, "question-detail.html", gin.H{ tc.html(ctx, http.StatusOK, "question-detail.html", siteInfo, gin.H{
"id": id, "id": id,
"answerid": answerid, "answerid": answerid,
"detail": detail, "detail": detail,
"answers": answers, "answers": answers,
"comments": comments, "comments": comments,
"scriptPath": tc.scriptPath,
"cssPath": tc.cssPath,
"siteinfo": siteInfo,
}) })
} }
@ -223,14 +216,12 @@ func (tc *TemplateController) TagList(ctx *gin.Context) {
return return
} }
page := templaterender.Paginator(req.Page, req.PageSize, data.Count) page := templaterender.Paginator(req.Page, req.PageSize, data.Count)
siteInfo := tc.SiteInfo(ctx) siteInfo := tc.SiteInfo(ctx)
siteInfo.Canonical = fmt.Sprintf("%s/tags", siteInfo.General.SiteUrl) siteInfo.Canonical = fmt.Sprintf("%s/tags", siteInfo.General.SiteUrl)
ctx.HTML(http.StatusOK, "tags.html", gin.H{ tc.html(ctx, http.StatusOK, "tags.html", siteInfo, gin.H{
"scriptPath": tc.scriptPath,
"cssPath": tc.cssPath,
"page": page, "page": page,
"data": data, "data": data,
"siteinfo": siteInfo,
}) })
} }
@ -260,15 +251,13 @@ func (tc *TemplateController) TagInfo(ctx *gin.Context) {
return return
} }
page := templaterender.Paginator(nowPage, req.PageSize, questionCount) page := templaterender.Paginator(nowPage, req.PageSize, questionCount)
siteInfo := tc.SiteInfo(ctx) siteInfo := tc.SiteInfo(ctx)
siteInfo.Canonical = fmt.Sprintf("%s/tags/%s", siteInfo.General.SiteUrl, tag) siteInfo.Canonical = fmt.Sprintf("%s/tags/%s", siteInfo.General.SiteUrl, tag)
ctx.HTML(http.StatusOK, "tag-detail.html", gin.H{ tc.html(ctx, http.StatusOK, "tag-detail.html", siteInfo, gin.H{
"tag": taginifo, "tag": taginifo,
"questionList": questionList, "questionList": questionList,
"questionCount": questionCount, "questionCount": questionCount,
"scriptPath": tc.scriptPath,
"cssPath": tc.cssPath,
"siteinfo": siteInfo,
"page": page, "page": page,
}) })
} }
@ -300,13 +289,8 @@ func (tc *TemplateController) UserInfo(ctx *gin.Context) {
siteInfo := tc.SiteInfo(ctx) siteInfo := tc.SiteInfo(ctx)
siteInfo.Canonical = fmt.Sprintf("%s/users/%s", siteInfo.General.SiteUrl, username) siteInfo.Canonical = fmt.Sprintf("%s/users/%s", siteInfo.General.SiteUrl, username)
tc.html(ctx, http.StatusOK, "homepage.html", siteInfo, gin.H{
ctx.HTML(http.StatusOK, "homepage.html", gin.H{
"siteinfo": siteInfo,
"userinfo": userinfo, "userinfo": userinfo,
"scriptPath": tc.scriptPath,
"cssPath": tc.cssPath,
"language": handler.GetLang(ctx),
"bio": template.HTML(userinfo.Info.BioHTML), "bio": template.HTML(userinfo.Info.BioHTML),
}) })
} }
@ -318,3 +302,13 @@ func (tc *TemplateController) Page404(ctx *gin.Context) {
"cssPath": tc.cssPath, "cssPath": tc.cssPath,
}) })
} }
func (tc *TemplateController) html(ctx *gin.Context, code int, tpl string, siteInfo *schema.TemplateSiteInfoResp, data gin.H) {
data["siteinfo"] = siteInfo
data["scriptPath"] = tc.scriptPath
data["cssPath"] = tc.cssPath
data["language"] = handler.GetLang(ctx)
data["timezone"] = siteInfo.Interface.TimeZone
ctx.HTML(code, tpl, data)
}

View File

@ -1,5 +1,5 @@
{{define "comment"}} {{define "comment"}}
{{range .}} {{range .comments}}
<div class="border-bottom py-2 comment-item border-top"> <div class="border-bottom py-2 comment-item border-top">
<div class="d-block"> <div class="d-block">
<div class="fmt fs-14"> <div class="fmt fs-14">
@ -11,8 +11,9 @@
<a href="/users/{{.Username}}">{{.UserDisplayName}}</a><span <a href="/users/{{.Username}}">{{.UserDisplayName}}</a><span
class="mx-1">•</span> class="mx-1">•</span>
<time <time
class="me-3" datetime="2022-11-24T02:04:49.000Z" class="me-3"
title="Nov 24, 2022 at 10:04">Nov 24 datetime="{{timeFormatISO $.timezone .CreatedAt}}"
title="{{translatorTimeFormatLongDate $.language $.timezone .CreatedAt}}">{{translatorTimeFormat $.language $.timezone .CreatedAt}}
</time> </time>
<button type="button" <button type="button"
class="me-3 btn-no-border p-0 link-secondary btn btn-link btn-sm"> class="me-3 btn-no-border p-0 link-secondary btn btn-link btn-sm">

View File

@ -1,5 +1,4 @@
{{template "header" . }} {{template "header" . }}
{{translatorTimeFormat $.language 1669920355}}
{{translator $.language "error.object.old_password_verification_failed"}} {{translator $.language "error.object.old_password_verification_failed"}}
<div class="pt-4 mt-2 mb-5 container"> <div class="pt-4 mt-2 mb-5 container">
<div class="justify-content-center row"> <div class="justify-content-center row">

View File

@ -8,13 +8,13 @@
</h1> </h1>
<div <div
class="d-flex flex-wrap align-items-center fs-14 mb-3 text-secondary"> class="d-flex flex-wrap align-items-center fs-14 mb-3 text-secondary">
<time class="me-3" datetime="2022-11-24T02:04:31.000Z" <time class="me-3"
title="Nov 24, 2022 at 10:04">Asked Nov 24 datetime="{{timeFormatISO $.timezone .detail.CreateTime}}"
title="{{translatorTimeFormatLongDate $.language $.timezone .detail.CreateTime}}">Asked {{translatorTimeFormat $.language $.timezone .detail.CreateTime}}
</time> </time>
<time class="me-3" <time class="me-3"
datetime="2022-11-28T09:48:16.000Z" datetime="{{timeFormatISO $.timezone .detail.UpdateTime}}"
title="Nov 28, 2022 at 17:48">Modified 23h title="{{translatorTimeFormatLongDate $.language $.timezone .detail.UpdateTime}}">Modified {{translatorTimeFormat $.language $.timezone .detail.UpdateTime}}
ago
</time> </time>
<div class="me-3">Viewed {{.detail.ViewCount}}</div> <div class="me-3">Viewed {{.detail.ViewCount}}</div>
@ -53,9 +53,8 @@
<div class="mb-3 mb-md-0 col-lg-3"> <div class="mb-3 mb-md-0 col-lg-3">
<a href="/posts/{{.detail.ID}}/timeline"> <a href="/posts/{{.detail.ID}}/timeline">
<time class="link-secondary fs-14" <time class="link-secondary fs-14"
datetime="2022-11-24T03:02:51.000Z" datetime="{{timeFormatISO $.timezone .detail.UpdateTime}}"
title="Nov 24, 2022 at 11:02">edited Nov title="{{translatorTimeFormatLongDate $.language $.timezone .detail.UpdateTime}}">edited {{translatorTimeFormat $.language $.timezone .detail.UpdateTime}}
24
</time> </time>
</a> </a>
</div> </div>
@ -79,8 +78,8 @@
</div> </div>
<a href="/posts/{{.detail.ID}}/timeline"> <a href="/posts/{{.detail.ID}}/timeline">
<time class="link-secondary" <time class="link-secondary"
datetime="2022-11-24T02:04:31.000Z" datetime="{{timeFormatISO $.timezone .detail.CreateTime}}"
title="Nov 24, 2022 at 10:04">asked Nov 24 title="{{translatorTimeFormatLongDate $.language $.timezone .detail.CreateTime}}">asked {{translatorTimeFormat $.language $.timezone .detail.CreateTime}}
</time> </time>
</a> </a>
</div> </div>
@ -88,7 +87,7 @@
</div> </div>
</div> </div>
<div class="comments-wrap"> <div class="comments-wrap">
{{template "comment" index $.comments $.detail.ID }} {{template "comment" (wrapComments (index $.comments $.detail.ID) $.language $.timezone)}}
</div> </div>
</div> </div>
<div class="d-flex align-items-center justify-content-between mt-5 mb-3" <div class="d-flex align-items-center justify-content-between mt-5 mb-3"
@ -130,8 +129,9 @@
<div class="mb-3 mb-md-0 col-lg-3"> <div class="mb-3 mb-md-0 col-lg-3">
<a href="/posts/{{$.detail.ID}}/{{.ID}}/timeline"> <a href="/posts/{{$.detail.ID}}/{{.ID}}/timeline">
<time <time
class="link-secondary fs-14" datetime="2022-11-28T09:48:16.000Z" class="link-secondary fs-14"
title="Nov 28, 2022 at 17:48">edited 23h ago datetime="{{timeFormatISO $.timezone .UpdateTime}}"
title="{{translatorTimeFormatLongDate $.language $.timezone .UpdateTime}}">edited {{translatorTimeFormat $.language $.timezone .UpdateTime}}
</time> </time>
</a> </a>
</div> </div>
@ -155,8 +155,9 @@
</div> </div>
<a href="/posts/{{$.detail.ID}}/{{.ID}}/timeline"> <a href="/posts/{{$.detail.ID}}/{{.ID}}/timeline">
<time <time
class="link-secondary" datetime="2022-11-28T09:48:16.000Z" class="link-secondary"
title="Nov 28, 2022 at 17:48">answered 23h ago datetime="{{timeFormatISO $.timezone .CreateTime}}"
title="{{translatorTimeFormatLongDate $.language $.timezone .CreateTime}}">answered {{translatorTimeFormat $.language $.timezone .CreateTime}}
</time> </time>
</a> </a>
</div> </div>
@ -164,7 +165,7 @@
</div> </div>
</div> </div>
<div class="comments-wrap"> <div class="comments-wrap">
{{template "comment" index $.comments .ID }} {{template "comment" (wrapComments (index $.comments .ID) $.language $.timezone)}}
</div> </div>
</div> </div>
{{end}} {{end}}

View File

@ -22,8 +22,8 @@
</div> </div>
<time class="text-secondary ms-1" <time class="text-secondary ms-1"
datetime="2022-11-29T07:38:07.000Z" datetime="{{timeFormatISO $.timezone .CreateTime}}"
title="Nov 29, 2022 at 15:38">asked 1h ago title="{{translatorTimeFormatLongDate $.language $.timezone .CreateTime}}">asked {{translatorTimeFormat $.language $.timezone .CreateTime}}
</time> </time>
</div> </div>
<div class="ms-0 ms-md-3 mt-2 mt-md-0"> <div class="ms-0 ms-md-3 mt-2 mt-md-0">