131 lines
3.9 KiB
Go
131 lines
3.9 KiB
Go
package main
|
|
|
|
import (
|
|
"fmt"
|
|
"net/http"
|
|
"net/url"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
// 多语言:模板与代码里的中文原文就是 key,按语言查表;查不到回落英文,再回落中文。
|
|
// 各语言表在 i18n_<lang>.go 里注册(translations[lang][中文] = 译文),占位符用 %s / %d。
|
|
|
|
type langInfo struct{ Code, Name, Short string }
|
|
|
|
var langs = []langInfo{
|
|
{"zh", "中文", "中"}, {"en", "English", "EN"}, {"ja", "日本語", "日"}, {"ko", "한국어", "한"}, {"ru", "Русский", "RU"}, {"es", "Español", "ES"},
|
|
{"pt", "Português", "PT"}, {"fr", "Français", "FR"}, {"de", "Deutsch", "DE"}, {"vi", "Tiếng Việt", "VI"}, {"id", "Bahasa Indonesia", "ID"}, {"tr", "Türkçe", "TR"},
|
|
}
|
|
|
|
// shortOf 当前语言的短标签,导航栏上显示(比地球图标更直观:一眼看出现在是哪种语言)。
|
|
func shortOf(code string) string {
|
|
for _, l := range langs {
|
|
if l.Code == code {
|
|
return l.Short
|
|
}
|
|
}
|
|
return "中"
|
|
}
|
|
|
|
var translations = map[string]map[string]string{}
|
|
|
|
func langOK(c string) bool {
|
|
for _, l := range langs {
|
|
if l.Code == c {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// tr 翻译一句(s 为中文原文),带参数时按 fmt 格式化。
|
|
func tr(lang, s string, args ...any) string {
|
|
if lang != "zh" && s != "" {
|
|
if t, ok := translations[lang][s]; ok {
|
|
s = t // 译文为空串也算有(中文量词在别的语言里就是没有)
|
|
} else if t, ok := translations["en"][s]; ok {
|
|
s = t
|
|
}
|
|
}
|
|
if len(args) > 0 {
|
|
return fmt.Sprintf(s, args...)
|
|
}
|
|
return s
|
|
}
|
|
|
|
// lang 当前请求的语言:cookie lang 优先,其次 Accept-Language,都没有就中文。
|
|
func (a *App) lang(r *http.Request) string {
|
|
if c := cookieVal(r, "lang"); langOK(c) {
|
|
return c
|
|
}
|
|
for _, part := range strings.Split(r.Header.Get("Accept-Language"), ",") {
|
|
tag := strings.ToLower(strings.TrimSpace(strings.SplitN(part, ";", 2)[0]))
|
|
if tag == "" || tag == "*" {
|
|
continue
|
|
}
|
|
primary := strings.SplitN(tag, "-", 2)[0]
|
|
if langOK(primary) {
|
|
return primary
|
|
}
|
|
return "en" // 列表外的语言一律英文
|
|
}
|
|
return "zh"
|
|
}
|
|
|
|
func (a *App) T(r *http.Request, s string, args ...any) string { return tr(a.lang(r), s, args...) }
|
|
|
|
// handleLang GET /lang?to=xx:记 cookie 一年,回到来时的页面。
|
|
func (a *App) handleLang(w http.ResponseWriter, r *http.Request) {
|
|
to := r.URL.Query().Get("to")
|
|
if !langOK(to) {
|
|
http.Redirect(w, r, "/", http.StatusFound)
|
|
return
|
|
}
|
|
a.setCookie(w, "lang", to, 365*24*3600)
|
|
back := "/"
|
|
if ref, err := url.Parse(r.Referer()); err == nil && ref.Path != "" && ref.Path != "/lang" {
|
|
back = ref.Path
|
|
if ref.RawQuery != "" {
|
|
back += "?" + ref.RawQuery
|
|
}
|
|
}
|
|
http.Redirect(w, r, back, http.StatusFound)
|
|
}
|
|
|
|
// agoL 相对时间(按语言)。
|
|
func agoL(lang string, t int64) string {
|
|
if t <= 0 {
|
|
return ""
|
|
}
|
|
d := time.Since(time.UnixMilli(t))
|
|
switch {
|
|
case d < time.Minute:
|
|
return tr(lang, "刚刚")
|
|
case d < time.Hour:
|
|
return tr(lang, "%d 分钟前", int(d.Minutes()))
|
|
case d < 24*time.Hour:
|
|
return tr(lang, "%d 小时前", int(d.Hours()))
|
|
case d < 30*24*time.Hour:
|
|
return tr(lang, "%d 天前", int(d.Hours()/24))
|
|
}
|
|
return time.UnixMilli(t).Format("2006-01-02")
|
|
}
|
|
|
|
// langFuncs 每种语言一套模板函数:T 与几个会输出文案的函数都绑定到该语言。
|
|
func langFuncs(lang string) map[string]any {
|
|
return map[string]any{
|
|
"T": func(s string, args ...any) string { return tr(lang, s, args...) },
|
|
"ago": func(t int64) string { return agoL(lang, t) },
|
|
"level": func(exp int64) LevelInfo {
|
|
li := levelOf(exp)
|
|
li.Title, li.NextT = tr(lang, li.Title), tr(lang, li.NextT)
|
|
return li
|
|
},
|
|
"beggarLevel": func(exp int64) string { return tr(lang, beggarLevel(exp)) },
|
|
"sectTitle": func(pos int) string { return tr(lang, sectTitle(pos)) },
|
|
"donorTier": func(e8 int64) string { return tr(lang, donorTier(e8)) },
|
|
"statusText": func(s string) string { return tr(lang, statusText(s)) },
|
|
}
|
|
}
|