Add i18n support

This commit is contained in:
cuigh 2017-10-12 11:04:27 +08:00
parent 63d9b9bd53
commit bfae6f7937
11 changed files with 196 additions and 75 deletions

View File

@ -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
View 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
View 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: 忘记密码?

View File

@ -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
}

View File

@ -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())

View File

@ -9,7 +9,7 @@ import (
const (
// Version is the version of Swirl
Version = "0.5.4"
Version = "0.5.5"
)
const (

View File

@ -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

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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">