mirror of
https://github.com/cuigh/swirl
synced 2024-12-28 14:51:57 +00:00
Add i18n support
This commit is contained in:
parent
63d9b9bd53
commit
bfae6f7937
@ -12,7 +12,6 @@ import (
|
||||
var Setting = &settingBiz{}
|
||||
|
||||
type settingBiz struct {
|
||||
loc *time.Location
|
||||
}
|
||||
|
||||
func (b *settingBiz) Get() (setting *model.Setting, err error) {
|
||||
@ -33,15 +32,3 @@ func (b *settingBiz) Update(setting *model.Setting, user web.User) (err error) {
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
func (b *settingBiz) Time(t time.Time) string {
|
||||
if b.loc == nil {
|
||||
// todo: auto refresh settings after update
|
||||
if s, err := b.Get(); err == nil && s != nil {
|
||||
b.loc = time.FixedZone(s.TimeZone.Name, int(s.TimeZone.Offset))
|
||||
} else {
|
||||
b.loc = time.Local
|
||||
}
|
||||
}
|
||||
return t.In(b.loc).Format("2006-01-02 15:04:05")
|
||||
}
|
||||
|
48
config/i18n/en.yml
Normal file
48
config/i18n/en.yml
Normal file
@ -0,0 +1,48 @@
|
||||
# common
|
||||
description: A Docker management tool, focused on Swarm cluster.
|
||||
copyright: All rights reserved.
|
||||
|
||||
# button
|
||||
button.login: Sign in
|
||||
button.logout: Sign out
|
||||
button.search: Search
|
||||
button.submit: Submit
|
||||
button.cancel: Cancel
|
||||
button.delete: Delete
|
||||
|
||||
# menu
|
||||
menu.dashboard: Dashboard
|
||||
menu.local: Local
|
||||
menu.image: Images
|
||||
menu.container: Containers
|
||||
menu.volume: Volumes
|
||||
menu.swarm: Swarm
|
||||
menu.registry: Registries
|
||||
menu.node: Nodes
|
||||
menu.network: Networks
|
||||
menu.service: Services
|
||||
menu.stack: Stacks
|
||||
menu.task: Tasks
|
||||
menu.secret: Secrets
|
||||
menu.config: Config
|
||||
menu.system: System
|
||||
menu.role: Roles
|
||||
menu.user: Users
|
||||
menu.setting: Settings
|
||||
menu.event: Events
|
||||
menu.version: Version
|
||||
menu.profile: Profile
|
||||
menu.password: Password
|
||||
|
||||
# dashboard
|
||||
dashboard.node: Nodes
|
||||
dashboard.network: Networks
|
||||
dashboard.service: Services
|
||||
dashboard.stack: Stacks
|
||||
dashboard.feature: Features
|
||||
|
||||
# login page
|
||||
login.title: Sign in to Swirl
|
||||
login.name: Login Name
|
||||
login.password: Password
|
||||
login.forgot-password: Forgot password?
|
48
config/i18n/zh.yml
Normal file
48
config/i18n/zh.yml
Normal file
@ -0,0 +1,48 @@
|
||||
# common
|
||||
description: 一个 Docker 管理工具,专注于 Swarm 集群。
|
||||
copyright: 保留所有权利。
|
||||
|
||||
# button
|
||||
button.login: 登录
|
||||
button.logout: 登出
|
||||
button.search: 搜索
|
||||
button.submit: 提交
|
||||
button.cancel: 取消
|
||||
button.delete: 删除
|
||||
|
||||
# menu
|
||||
menu.dashboard: 仪表盘
|
||||
menu.local: 单机
|
||||
menu.image: 镜像
|
||||
menu.container: 容器
|
||||
menu.volume: 数据卷
|
||||
menu.swarm: 集群
|
||||
menu.registry: 镜像仓库
|
||||
menu.node: 节点
|
||||
menu.network: 网络
|
||||
menu.service: 服务
|
||||
menu.stack: 编排
|
||||
menu.task: 任务
|
||||
menu.secret: 私密配置
|
||||
menu.config: 常规配置
|
||||
menu.system: 系统
|
||||
menu.role: 角色
|
||||
menu.user: 用户
|
||||
menu.setting: 设置
|
||||
menu.event: 事件
|
||||
menu.version: 版本
|
||||
menu.profile: 资料
|
||||
menu.password: 密码
|
||||
|
||||
# dashboard
|
||||
dashboard.node: 节点
|
||||
dashboard.network: 网络
|
||||
dashboard.service: 服务
|
||||
dashboard.stack: 编排
|
||||
dashboard.feature: 功能
|
||||
|
||||
# login page
|
||||
login.title: 登录到 Swirl
|
||||
login.name: 登录名
|
||||
login.password: 密码
|
||||
login.forgot-password: 忘记密码?
|
@ -3,6 +3,8 @@ package mongo
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"time"
|
||||
|
||||
"github.com/cuigh/auxo/log"
|
||||
"gopkg.in/mgo.v2"
|
||||
)
|
||||
@ -30,6 +32,9 @@ var (
|
||||
mgo.Index{Key: []string{"name"}},
|
||||
mgo.Index{Key: []string{"-time"}},
|
||||
},
|
||||
"template": {
|
||||
mgo.Index{Key: []string{"name"}, Unique: true},
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
@ -59,7 +64,7 @@ func New(addr string) (*Dao, error) {
|
||||
return nil, errors.New("database address must be configured for mongo storage")
|
||||
}
|
||||
|
||||
s, err := mgo.Dial(addr)
|
||||
s, err := mgo.DialWithTimeout(addr, time.Second*5)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
9
main.go
9
main.go
@ -1,6 +1,7 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
@ -19,6 +20,11 @@ import (
|
||||
)
|
||||
|
||||
func main() {
|
||||
setting, err := biz.Setting.Get()
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("Load setting failed: %v", err))
|
||||
}
|
||||
|
||||
// customize error handler
|
||||
web.DefaultErrorHandler.OnCode(http.StatusNotFound, func(ctx web.Context, err error) {
|
||||
ctx.Redirect("/404")
|
||||
@ -31,7 +37,8 @@ func main() {
|
||||
|
||||
// set render/validator..
|
||||
ws.Renderer = jet.New().SetDebug(config.App().Debug).
|
||||
AddFunc("time", biz.Setting.Time).
|
||||
AddFunc("time", misc.FormatTime(setting.TimeZone.Offset)).
|
||||
AddFunc("i18n", misc.Message(setting.Language)).
|
||||
AddFuncs(misc.Funcs).
|
||||
AddVariable("version", misc.Version).
|
||||
AddVariable("go_version", runtime.Version())
|
||||
|
@ -9,7 +9,7 @@ import (
|
||||
|
||||
const (
|
||||
// Version is the version of Swirl
|
||||
Version = "0.5.4"
|
||||
Version = "0.5.5"
|
||||
)
|
||||
|
||||
const (
|
||||
|
29
misc/util.go
29
misc/util.go
@ -6,6 +6,9 @@ import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/cuigh/auxo/util/i18n"
|
||||
)
|
||||
|
||||
var Funcs = map[string]interface{}{
|
||||
@ -43,6 +46,32 @@ var Funcs = map[string]interface{}{
|
||||
},
|
||||
}
|
||||
|
||||
func Message(lang string) func(key string, args ...interface{}) string {
|
||||
t, err := i18n.Find(lang, "en")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return func(key string, args ...interface{}) string {
|
||||
return t.Format(key, args...)
|
||||
}
|
||||
}
|
||||
|
||||
func FormatTime(offset int32) func(t time.Time) string {
|
||||
const layout = "2006-01-02 15:04:05"
|
||||
|
||||
var loc *time.Location
|
||||
if offset == 0 {
|
||||
loc = time.Local
|
||||
} else {
|
||||
loc = time.FixedZone("", int(offset))
|
||||
}
|
||||
|
||||
return func(t time.Time) string {
|
||||
return t.In(loc).Format(layout)
|
||||
}
|
||||
}
|
||||
|
||||
func Page(count, pageIndex, pageSize int) (start, end int) {
|
||||
start = pageSize * (pageIndex - 1)
|
||||
end = pageSize * pageIndex
|
||||
|
@ -23,38 +23,38 @@
|
||||
</div>
|
||||
<div id="menu-top" class="navbar-menu">
|
||||
<div class="navbar-start">
|
||||
<a class="navbar-item" href="/">Dashboard</a>
|
||||
<a class="navbar-item" href="/">{{ i18n("menu.dashboard") }}</a>
|
||||
<div class="navbar-item has-dropdown is-hoverable">
|
||||
<div class="navbar-link">Local</div>
|
||||
<div class="navbar-link">{{ i18n("menu.local") }}</div>
|
||||
<div class="navbar-dropdown ">
|
||||
<a class="navbar-item" href="/image/">Images</a>
|
||||
<a class="navbar-item" href="/container/">Containers</a>
|
||||
<a class="navbar-item" href="/volume/">Volumes</a>
|
||||
<a class="navbar-item" href="/image/">{{ i18n("menu.image") }}</a>
|
||||
<a class="navbar-item" href="/container/">{{ i18n("menu.container") }}</a>
|
||||
<a class="navbar-item" href="/volume/">{{ i18n("menu.volume") }}</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="navbar-item has-dropdown is-hoverable">
|
||||
<div class="navbar-link">Swarm</div>
|
||||
<div class="navbar-link">{{ i18n("menu.swarm") }}</div>
|
||||
<div class="navbar-dropdown ">
|
||||
<a class="navbar-item" href="/registry/">Registries</a>
|
||||
<a class="navbar-item" href="/node/">Nodes</a>
|
||||
<a class="navbar-item" href="/network/">Networks</a>
|
||||
<a class="navbar-item" href="/service/">Services</a>
|
||||
<a class="navbar-item" href="/stack/task/">Stacks</a>
|
||||
<a class="navbar-item" href="/secret/">Secrets</a>
|
||||
<a class="navbar-item" href="/config/">Configs</a>
|
||||
<a class="navbar-item" href="/registry/">{{ i18n("menu.registry") }}</a>
|
||||
<a class="navbar-item" href="/node/">{{ i18n("menu.node") }}</a>
|
||||
<a class="navbar-item" href="/network/">{{ i18n("menu.network") }}</a>
|
||||
<a class="navbar-item" href="/service/">{{ i18n("menu.service") }}</a>
|
||||
<a class="navbar-item" href="/stack/task/">{{ i18n("menu.stack") }}</a>
|
||||
<a class="navbar-item" href="/secret/">{{ i18n("menu.secret") }}</a>
|
||||
<a class="navbar-item" href="/config/">{{ i18n("menu.config") }}</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="navbar-item has-dropdown is-hoverable">
|
||||
<div class="navbar-link">System</div>
|
||||
<div class="navbar-link">{{ i18n("menu.system") }}</div>
|
||||
<div class="navbar-dropdown ">
|
||||
<a class="navbar-item" href="/system/role/">Roles</a>
|
||||
<a class="navbar-item" href="/system/user/">Users</a>
|
||||
<a class="navbar-item" href="/system/setting/">Settings</a>
|
||||
<a class="navbar-item" href="/system/event/">Events</a>
|
||||
<a class="navbar-item" href="/system/role/">{{ i18n("menu.role") }}</a>
|
||||
<a class="navbar-item" href="/system/user/">{{ i18n("menu.user") }}</a>
|
||||
<a class="navbar-item" href="/system/setting/">{{ i18n("menu.setting") }}</a>
|
||||
<a class="navbar-item" href="/system/event/">{{ i18n("menu.event") }}</a>
|
||||
<hr class="navbar-divider">
|
||||
<div class="navbar-item">
|
||||
<div class="navbar-content">
|
||||
<strong>Version {{ version }}({{ go_version }})</strong>
|
||||
<strong>{{ i18n("menu.version") }} {{ version }}({{ go_version }})</strong>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -67,14 +67,14 @@
|
||||
<div class="navbar-item has-dropdown is-hoverable is-narrow">
|
||||
<div class="navbar-link is-narrow"><span class="icon"><i class="fa fa-user"></i></span></div>
|
||||
<div class="navbar-dropdown is-right">
|
||||
<a class="navbar-item" href="/profile/">Profile</a>
|
||||
<a class="navbar-item" href="/profile/password">Password</a>
|
||||
<a class="navbar-item" href="/profile/">{{ i18n("menu.profile") }}</a>
|
||||
<a class="navbar-item" href="/profile/password">{{ i18n("menu.password") }}</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="navbar-item">
|
||||
<div class="field">
|
||||
<p class="control">
|
||||
<a class="button is-outlined tooltip is-tooltip-bottom" href="/logout" data-tooltip="Sign out">
|
||||
<a class="button is-outlined tooltip is-tooltip-bottom" href="/logout" data-tooltip="{{ i18n("button.logout") }}">
|
||||
<span class="icon">
|
||||
<i class="fa fa-sign-out"></i>
|
||||
</span>
|
||||
@ -91,7 +91,7 @@
|
||||
<footer class="footer" style="padding-bottom: 3em">
|
||||
<div class="container">
|
||||
<div class="content has-text-centered">
|
||||
<p>© 2017 <a href="https://github.com/cuigh/swirl" target="_blank">cuigh</a>. All rights reserved.</p>
|
||||
<p>© 2017 <a href="https://github.com/cuigh/swirl" target="_blank">cuigh</a>. {{ i18n("copyright") }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
|
@ -34,15 +34,13 @@
|
||||
<h1 class="title is-2">
|
||||
SWIRL <span class="tag is-success">V{{ version }}</span>
|
||||
</h1>
|
||||
<h2 class="subtitle is-5">
|
||||
Another Docker Swarm Management System.
|
||||
</h2>
|
||||
<h2 class="subtitle is-5">{{ i18n("description") }}</h2>
|
||||
<p>
|
||||
<a id="btn-add" class="button is-primary" href="https://github.com/cuigh/swirl" target="_blank">
|
||||
<span class="icon">
|
||||
<i class="fa fa-github"></i>
|
||||
</span>
|
||||
<span>Fork me</span>
|
||||
<span>GitHub</span>
|
||||
</a>
|
||||
{*<a id="btn-add" class="button is-success" href="https://cuigh.tech/swirl" target="_blank">*}
|
||||
{*<span class="icon">*}
|
||||
@ -60,55 +58,54 @@
|
||||
WARNING: Swirl is running on basic mode now, some features are disabled. You can enable full features by configuring database.
|
||||
</div>
|
||||
<div class="columns">
|
||||
{{yield infobox(style="success", icon="cubes", text="Nodes", url="/node/", count=.NodeCount)}}
|
||||
{{yield infobox(style="info", icon="globe", text="Networks", url="/network/", count=.NetworkCount)}}
|
||||
{{yield infobox(style="danger", icon="ticket", text="Services", url="/service/", count=.ServiceCount)}}
|
||||
{{yield infobox(style="primary", icon="server", text="Stacks", url="/stack/task/", count=.StackCount)}}
|
||||
{{yield infobox(style="success", icon="cubes", text=i18n("dashboard.node"), url="/node/", count=.NodeCount)}}
|
||||
{{yield infobox(style="info", icon="globe", text=i18n("dashboard.network"), url="/network/", count=.NetworkCount)}}
|
||||
{{yield infobox(style="danger", icon="ticket", text=i18n("dashboard.service"), url="/service/", count=.ServiceCount)}}
|
||||
{{yield infobox(style="primary", icon="server", text=i18n("dashboard.stack"), url="/stack/task/", count=.StackCount)}}
|
||||
</div>
|
||||
<hr>
|
||||
<h3 class="title is-5">Features</h3>
|
||||
<h3 class="title is-5">{{ i18n("dashboard.feature") }}</h3>
|
||||
<div class="columns">
|
||||
<div class="column is-4">
|
||||
<div class="card is-fullwidth">
|
||||
<header class="card-header">
|
||||
<h4 class="card-header-title"><strong>Local</strong></h4>
|
||||
<h4 class="card-header-title"><strong>{{ i18n("menu.local") }}</strong></h4>
|
||||
</header>
|
||||
<div class="card-content">
|
||||
{*{{yield progress(title="System", percent=0)}}*}
|
||||
{{yield progress(title="Image", percent=100)}}
|
||||
{{yield progress(title="Container", percent=80)}}
|
||||
{{yield progress(title="Volume", percent=100)}}
|
||||
{{yield progress(title=i18n("menu.image"), percent=100)}}
|
||||
{{yield progress(title=i18n("menu.container"), percent=80)}}
|
||||
{{yield progress(title=i18n("menu.volume"), percent=100)}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="column is-4">
|
||||
<div class="card is-fullwidth">
|
||||
<header class="card-header">
|
||||
<h4 class="card-header-title"><strong>Swarm</strong></h4>
|
||||
<h4 class="card-header-title"><strong>{{ i18n("menu.swarm") }}</strong></h4>
|
||||
</header>
|
||||
<div class="card-content">
|
||||
{{yield progress(title="Registry", percent=100)}}
|
||||
{{yield progress(title="Node", percent=100)}}
|
||||
{{yield progress(title="Network", percent=100)}}
|
||||
{{yield progress(title="Service", percent=100)}}
|
||||
{{yield progress(title="Stack", percent=100)}}
|
||||
{{yield progress(title="Task", percent=100)}}
|
||||
{{yield progress(title="Secret", percent=100)}}
|
||||
{{yield progress(title="Config", percent=100)}}
|
||||
{{yield progress(title=i18n("menu.registry"), percent=100)}}
|
||||
{{yield progress(title=i18n("menu.node"), percent=100)}}
|
||||
{{yield progress(title=i18n("menu.network"), percent=100)}}
|
||||
{{yield progress(title=i18n("menu.service"), percent=100)}}
|
||||
{{yield progress(title=i18n("menu.stack"), percent=100)}}
|
||||
{{yield progress(title=i18n("menu.task"), percent=100)}}
|
||||
{{yield progress(title=i18n("menu.secret"), percent=100)}}
|
||||
{{yield progress(title=i18n("menu.config"), percent=100)}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="column is-4">
|
||||
<div class="card is-fullwidth">
|
||||
<header class="card-header">
|
||||
<h4 class="card-header-title"><strong>System</strong></h4>
|
||||
<h4 class="card-header-title"><strong>{{ i18n("menu.system") }}</strong></h4>
|
||||
</header>
|
||||
<div class="card-content">
|
||||
{{yield progress(title="Profile", percent=100)}}
|
||||
{{yield progress(title="Role", percent=100)}}
|
||||
{{yield progress(title="User", percent=100)}}
|
||||
{{yield progress(title="Setting", percent=100)}}
|
||||
{{yield progress(title="Event", percent=80)}}
|
||||
{{yield progress(title=i18n("menu.profile"), percent=100)}}
|
||||
{{yield progress(title=i18n("menu.role"), percent=100)}}
|
||||
{{yield progress(title=i18n("menu.user"), percent=100)}}
|
||||
{{yield progress(title=i18n("menu.setting"), percent=100)}}
|
||||
{{yield progress(title=i18n("menu.event"), percent=80)}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -5,11 +5,11 @@
|
||||
<div class="container">
|
||||
<div class="columns">
|
||||
<div class="column is-4 is-offset-4">
|
||||
<h1 class="title has-text-centered">Sign in to Swirl</h1>
|
||||
<h1 class="title has-text-centered">{{ i18n("login.title") }}</h1>
|
||||
<form method="post" data-form="ajax-json">
|
||||
<div class="box">
|
||||
<div class="field">
|
||||
<label class="label">Login Name</label>
|
||||
<label class="label">{{ i18n("login.name") }}</label>
|
||||
<p class="control has-icons-left">
|
||||
<input name="name" class="input" type="text" placeholder="" data-v-rule="native" required>
|
||||
<span class="icon is-small is-left">
|
||||
@ -18,7 +18,7 @@
|
||||
</p>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label class="label">Password</label>
|
||||
<label class="label">{{ i18n("login.password") }}</label>
|
||||
<p class="control has-icons-left">
|
||||
<input name="password" class="input" type="password" data-v-rule="native" required>
|
||||
<span class="icon is-small is-left">
|
||||
@ -29,13 +29,13 @@
|
||||
<hr>
|
||||
<div class="field">
|
||||
<p class="control">
|
||||
<button class="button is-primary" type="submit">Sign in</button>
|
||||
<button class="button is-primary" type="submit">{{ i18n("button.login") }}</button>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
<p class="has-text-centered">
|
||||
<a href="/password_reset">Forgot password?</a>
|
||||
<a href="/password_reset">{{ i18n("login.forgot-password") }}</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -146,8 +146,8 @@
|
||||
<div class="control has-icons-left">
|
||||
<div class="select">
|
||||
<select name="lang">
|
||||
<option value="en">English</option>
|
||||
{*<option value="zh">Chinese</option>*}
|
||||
{{ yield option(value="en", label="English", selected=.Setting.Language) }}
|
||||
{{ yield option(value="zh", label="中文", selected=.Setting.Language) }}
|
||||
</select>
|
||||
</div>
|
||||
<div class="icon is-left">
|
||||
|
Loading…
Reference in New Issue
Block a user