add translations
This commit is contained in:
parent
b749388104
commit
24d63f4c98
|
@ -19,29 +19,39 @@ func logTemplateTime(name string, from time.Time) {
|
||||||
func (s *Site) renderTemplatePage(templateFname string, data interface{}) http.Handler {
|
func (s *Site) renderTemplatePage(templateFname string, data interface{}) http.Handler {
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
defer logTemplateTime(templateFname, time.Now())
|
defer logTemplateTime(templateFname, time.Now())
|
||||||
s.tlock.RLock()
|
|
||||||
defer s.tlock.RUnlock()
|
|
||||||
|
|
||||||
var t *template.Template
|
getTranslation := func(group, key string, vals ...interface{}) string {
|
||||||
var err error
|
var lc locale
|
||||||
|
var ok bool
|
||||||
if s.templates[templateFname] == nil {
|
locale, err := GetPreferredLocale(r)
|
||||||
t, err = template.ParseFiles("templates/base.html", "templates/"+templateFname)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
ln.Error(r.Context(), err)
|
||||||
ln.Error(context.Background(), err, ln.F{"action": "renderTemplatePage", "page": templateFname})
|
lc = s.t.locales["en"]
|
||||||
fmt.Fprintf(w, "error: %v", err)
|
goto overwith
|
||||||
}
|
}
|
||||||
|
|
||||||
ln.Log(context.Background(), ln.F{"action": "loaded_new_template", "fname": templateFname})
|
lc, ok = s.t.Get(locale.Lang)
|
||||||
|
if !ok {
|
||||||
|
ln.Log(r.Context(), ln.F{"msg": "unknown language requested", "lang": locale.Lang, "header": r.Header.Get("Accept-Language")})
|
||||||
|
lc = s.t.locales["en"]
|
||||||
|
goto overwith
|
||||||
|
}
|
||||||
|
|
||||||
s.tlock.RUnlock()
|
overwith:
|
||||||
s.tlock.Lock()
|
return lc.Value(group, key, vals...)
|
||||||
s.templates[templateFname] = t
|
}
|
||||||
s.tlock.Unlock()
|
funcMap := template.FuncMap{
|
||||||
s.tlock.RLock()
|
"trans": getTranslation,
|
||||||
} else {
|
}
|
||||||
t = s.templates[templateFname]
|
|
||||||
|
var t *template.Template = template.New(templateFname).Funcs(funcMap)
|
||||||
|
var err error
|
||||||
|
|
||||||
|
t, err = t.ParseFiles("templates/base.html", "templates/"+templateFname)
|
||||||
|
if err != nil {
|
||||||
|
w.WriteHeader(http.StatusInternalServerError)
|
||||||
|
ln.Error(context.Background(), err, ln.F{"action": "renderTemplatePage", "page": templateFname})
|
||||||
|
fmt.Fprintf(w, "error: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = t.Execute(w, data)
|
err = t.Execute(w, data)
|
||||||
|
|
|
@ -0,0 +1,46 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
type locale map[string]map[string]string
|
||||||
|
|
||||||
|
func (l locale) Value(group, key string, args ...interface{}) string {
|
||||||
|
sg, ok := l[group]
|
||||||
|
if !ok {
|
||||||
|
return "no group " + group
|
||||||
|
}
|
||||||
|
|
||||||
|
result, ok := sg[key]
|
||||||
|
if !ok {
|
||||||
|
return fmt.Sprintf("in group %s, no key %s", group, key)
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Sprintf(result, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
type translations struct {
|
||||||
|
locales map[string]locale
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *translations) LoadLocale(name string, r io.Reader) error {
|
||||||
|
l := locale{}
|
||||||
|
d := json.NewDecoder(r)
|
||||||
|
err := d.Decode(&l)
|
||||||
|
if err == nil {
|
||||||
|
t.locales[name] = l
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *translations) Get(name string) (locale, bool) {
|
||||||
|
l, ok := t.locales[name]
|
||||||
|
if !ok {
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
|
||||||
|
return l, ok
|
||||||
|
}
|
|
@ -0,0 +1,97 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Locale is locale value from the Accept-Language header in request
|
||||||
|
type Locale struct {
|
||||||
|
Lang, Country string
|
||||||
|
Qual float64
|
||||||
|
}
|
||||||
|
|
||||||
|
// Name returns the locale value in 'lang' or 'lang_country' format
|
||||||
|
// eg: de_DE, en_US, gb
|
||||||
|
func (l *Locale) Name() string {
|
||||||
|
if len(l.Country) > 0 {
|
||||||
|
return l.Lang + "_" + l.Country
|
||||||
|
}
|
||||||
|
return l.Lang
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseLocale creates a Locale from a locale string
|
||||||
|
func ParseLocale(locale string) Locale {
|
||||||
|
locsplt := strings.Split(locale, "_")
|
||||||
|
resp := Locale{}
|
||||||
|
resp.Lang = locsplt[0]
|
||||||
|
if len(locsplt) > 1 {
|
||||||
|
resp.Country = locsplt[1]
|
||||||
|
}
|
||||||
|
return resp
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
acceptLanguage = "Accept-Language"
|
||||||
|
)
|
||||||
|
|
||||||
|
func supportedLocales(alstr string) []Locale {
|
||||||
|
locales := make([]Locale, 0)
|
||||||
|
alstr = strings.Replace(alstr, " ", "", -1)
|
||||||
|
if alstr == "" {
|
||||||
|
return locales
|
||||||
|
}
|
||||||
|
al := strings.Split(alstr, ",")
|
||||||
|
for _, lstr := range al {
|
||||||
|
locales = append(locales, Locale{
|
||||||
|
Lang: parseLang(lstr),
|
||||||
|
Country: parseCountry(lstr),
|
||||||
|
Qual: parseQual(lstr),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return locales
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetLocales returns supported locales for the given requet
|
||||||
|
func GetLocales(r *http.Request) []Locale {
|
||||||
|
return supportedLocales(r.Header.Get(acceptLanguage))
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetPreferredLocale return preferred locale for the given reuqest
|
||||||
|
// returns error if there is no preferred locale
|
||||||
|
func GetPreferredLocale(r *http.Request) (*Locale, error) {
|
||||||
|
locales := GetLocales(r)
|
||||||
|
if len(locales) == 0 {
|
||||||
|
return &Locale{}, errors.New("No locale found")
|
||||||
|
}
|
||||||
|
return &locales[0], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseLang(val string) string {
|
||||||
|
locale := strings.Split(val, ";")[0]
|
||||||
|
lang := strings.Split(locale, "-")[0]
|
||||||
|
return lang
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseCountry(val string) string {
|
||||||
|
locale := strings.Split(val, ";")[0]
|
||||||
|
spl := strings.Split(locale, "-")
|
||||||
|
if len(spl) > 1 {
|
||||||
|
return spl[1]
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseQual(val string) float64 {
|
||||||
|
spl := strings.Split(val, ";")
|
||||||
|
if len(spl) > 1 {
|
||||||
|
qual, err := strconv.ParseFloat(strings.Split(spl[1], "=")[1], 64)
|
||||||
|
if err != nil {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
return qual
|
||||||
|
}
|
||||||
|
return 1
|
||||||
|
}
|
|
@ -9,7 +9,6 @@ import (
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/Xe/jsonfeed"
|
"github.com/Xe/jsonfeed"
|
||||||
|
@ -46,10 +45,8 @@ type Site struct {
|
||||||
|
|
||||||
mux *http.ServeMux
|
mux *http.ServeMux
|
||||||
|
|
||||||
templates map[string]*template.Template
|
|
||||||
tlock sync.RWMutex
|
|
||||||
|
|
||||||
segment analytics.Client
|
segment analytics.Client
|
||||||
|
t *translations
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Site) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
func (s *Site) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
|
@ -78,31 +75,49 @@ func Build() (*Site, error) {
|
||||||
Date string
|
Date string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
t := &translations{
|
||||||
|
locales: map[string]locale{},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, lang := range []string{"en", "tp"} {
|
||||||
|
fin, err := os.Open(filepath.Join("locales", lang+".json"))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer fin.Close()
|
||||||
|
|
||||||
|
err = t.LoadLocale(lang, fin)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
l := t.locales["en"]
|
||||||
|
|
||||||
s := &Site{
|
s := &Site{
|
||||||
rssFeed: &feeds.Feed{
|
rssFeed: &feeds.Feed{
|
||||||
Title: "Christine Dodrill's Blog",
|
Title: l.Value("blog", "title"),
|
||||||
Link: &feeds.Link{Href: "https://christine.website/blog"},
|
Link: &feeds.Link{Href: "https://christine.website/blog"},
|
||||||
Description: "My blog posts and rants about various technology things.",
|
Description: l.Value("blog", "description"),
|
||||||
Author: &feeds.Author{Name: "Christine Dodrill", Email: "me@christine.website"},
|
Author: &feeds.Author{Name: "Christine Dodrill", Email: "me@christine.website"},
|
||||||
Created: bootTime,
|
Created: bootTime,
|
||||||
Copyright: "This work is copyright Christine Dodrill. My viewpoints are my own and not the view of any employer past, current or future.",
|
Copyright: l.Value("meta", "rss_copyright"),
|
||||||
},
|
},
|
||||||
jsonFeed: &jsonfeed.Feed{
|
jsonFeed: &jsonfeed.Feed{
|
||||||
Version: jsonfeed.CurrentVersion,
|
Version: jsonfeed.CurrentVersion,
|
||||||
Title: "Christine Dodrill's Blog",
|
Title: l.Value("blog", "title"),
|
||||||
HomePageURL: "https://christine.website",
|
HomePageURL: "https://christine.website",
|
||||||
FeedURL: "https://christine.website/blog.json",
|
FeedURL: "https://christine.website/blog.json",
|
||||||
Description: "My blog posts and rants about various technology things.",
|
Description: l.Value("blog", "description"),
|
||||||
UserComment: "This is a JSON feed of my blogposts. For more information read: https://jsonfeed.org/version/1",
|
UserComment: l.Value("meta", "json_feed"),
|
||||||
Icon: icon,
|
Icon: icon,
|
||||||
Favicon: icon,
|
Favicon: icon,
|
||||||
Author: jsonfeed.Author{
|
Author: jsonfeed.Author{
|
||||||
Name: "Christine Dodrill",
|
Name: l.Value("header", "name"),
|
||||||
Avatar: icon,
|
Avatar: icon,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
mux: http.NewServeMux(),
|
mux: http.NewServeMux(),
|
||||||
templates: map[string]*template.Template{},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if wk := os.Getenv("SEGMENT_WRITE_KEY"); wk != "" {
|
if wk := os.Getenv("SEGMENT_WRITE_KEY"); wk != "" {
|
||||||
|
|
7
go.mod
7
go.mod
|
@ -1,18 +1,17 @@
|
||||||
module github.com/Xe/site
|
module github.com/Xe/site
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/GeertJohan/go.rice v0.0.0-20170420135705-c02ca9a983da // indirect
|
|
||||||
github.com/Xe/gopreload v0.0.0-20170326043426-a00a8beb369c
|
github.com/Xe/gopreload v0.0.0-20170326043426-a00a8beb369c
|
||||||
github.com/Xe/jsonfeed v0.0.0-20170520170432-e21591505612
|
github.com/Xe/jsonfeed v0.0.0-20170520170432-e21591505612
|
||||||
github.com/Xe/ln v0.0.0-20170921000907-466e05b2ef3e
|
github.com/Xe/ln v0.0.0-20170921000907-466e05b2ef3e
|
||||||
github.com/daaku/go.zipexe v0.0.0-20150329023125-a5fe2436ffcb // indirect
|
github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 // indirect
|
||||||
|
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||||
github.com/google/gops v0.3.2
|
github.com/google/gops v0.3.2
|
||||||
github.com/gorilla/feeds v1.0.0
|
github.com/gorilla/feeds v1.0.0
|
||||||
github.com/jtolds/qod v0.0.0-20170925014538-3abb44dfc7ba // indirect
|
|
||||||
github.com/kardianos/osext v0.0.0-20170510131534-ae77be60afb1 // indirect
|
github.com/kardianos/osext v0.0.0-20170510131534-ae77be60afb1 // indirect
|
||||||
github.com/kr/pretty v0.1.0 // indirect
|
github.com/kr/pretty v0.1.0 // indirect
|
||||||
github.com/magefile/mage v0.0.0-20171025021237-ab3ca2f6f855 // indirect
|
|
||||||
github.com/pkg/errors v0.8.0 // indirect
|
github.com/pkg/errors v0.8.0 // indirect
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
github.com/russross/blackfriday v0.0.0-20170806171014-cadec560ec52
|
github.com/russross/blackfriday v0.0.0-20170806171014-cadec560ec52
|
||||||
github.com/segmentio/backo-go v0.0.0-20160424052352-204274ad699c // indirect
|
github.com/segmentio/backo-go v0.0.0-20160424052352-204274ad699c // indirect
|
||||||
github.com/shurcooL/sanitized_anchor_name v0.0.0-20170918181015-86672fcb3f95 // indirect
|
github.com/shurcooL/sanitized_anchor_name v0.0.0-20170918181015-86672fcb3f95 // indirect
|
||||||
|
|
10
go.sum
10
go.sum
|
@ -1,17 +1,17 @@
|
||||||
github.com/GeertJohan/go.rice v0.0.0-20170420135705-c02ca9a983da/go.mod h1:DgrzXonpdQbfN3uYaGz1EG4Sbhyum/MMIn6Cphlh2bw=
|
|
||||||
github.com/Xe/gopreload v0.0.0-20170326043426-a00a8beb369c h1:lqTJqaoonxgJMvvfl1ukr/3qCEGWC0nQxzPezbJrhHs=
|
github.com/Xe/gopreload v0.0.0-20170326043426-a00a8beb369c h1:lqTJqaoonxgJMvvfl1ukr/3qCEGWC0nQxzPezbJrhHs=
|
||||||
github.com/Xe/gopreload v0.0.0-20170326043426-a00a8beb369c/go.mod h1:0aSWHJguPNHo6zlU7A4Ktua1A/VUr5Jdr1QZ2amOkAQ=
|
github.com/Xe/gopreload v0.0.0-20170326043426-a00a8beb369c/go.mod h1:0aSWHJguPNHo6zlU7A4Ktua1A/VUr5Jdr1QZ2amOkAQ=
|
||||||
github.com/Xe/jsonfeed v0.0.0-20170520170432-e21591505612 h1:5cPld6YTMozzm3lK9VCnOErgoFbADM2hZc4KDu0YNKs=
|
github.com/Xe/jsonfeed v0.0.0-20170520170432-e21591505612 h1:5cPld6YTMozzm3lK9VCnOErgoFbADM2hZc4KDu0YNKs=
|
||||||
github.com/Xe/jsonfeed v0.0.0-20170520170432-e21591505612/go.mod h1:UKXAbYA/G9yE+cPbpfiP7FlT6Kr3N2mbEjQEJ9hT004=
|
github.com/Xe/jsonfeed v0.0.0-20170520170432-e21591505612/go.mod h1:UKXAbYA/G9yE+cPbpfiP7FlT6Kr3N2mbEjQEJ9hT004=
|
||||||
github.com/Xe/ln v0.0.0-20170921000907-466e05b2ef3e h1:uMC/C/zBov+PItx2c6Vax4lt37X+2V5X7NWRcXsxuzE=
|
github.com/Xe/ln v0.0.0-20170921000907-466e05b2ef3e h1:uMC/C/zBov+PItx2c6Vax4lt37X+2V5X7NWRcXsxuzE=
|
||||||
github.com/Xe/ln v0.0.0-20170921000907-466e05b2ef3e/go.mod h1:hDOCtu3XM9SVMB0UHBilUvRbZ0JKMI7IDbvBzwclIb0=
|
github.com/Xe/ln v0.0.0-20170921000907-466e05b2ef3e/go.mod h1:hDOCtu3XM9SVMB0UHBilUvRbZ0JKMI7IDbvBzwclIb0=
|
||||||
github.com/daaku/go.zipexe v0.0.0-20150329023125-a5fe2436ffcb/go.mod h1:U0vRfAucUOohvdCxt5MWLF+TePIL0xbCkbKIiV8TQCE=
|
github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869 h1:DDGfHa7BWjL4YnC6+E63dPcxHo2sUxDIu8g3QgEJdRY=
|
||||||
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
|
github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4=
|
||||||
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/google/gops v0.3.2 h1:n9jMkrye8dh3WQ0IxG5dzLRIhQeZDZoGaj0D7T7x7hQ=
|
github.com/google/gops v0.3.2 h1:n9jMkrye8dh3WQ0IxG5dzLRIhQeZDZoGaj0D7T7x7hQ=
|
||||||
github.com/google/gops v0.3.2/go.mod h1:pMQgrscwEK/aUSW1IFSaBPbJX82FPHWaSoJw1axQfD0=
|
github.com/google/gops v0.3.2/go.mod h1:pMQgrscwEK/aUSW1IFSaBPbJX82FPHWaSoJw1axQfD0=
|
||||||
github.com/gorilla/feeds v1.0.0 h1:EbkEvaYf+PXhYNHS20heBG7Rl2X6Zy8l11ZBWAHkWqE=
|
github.com/gorilla/feeds v1.0.0 h1:EbkEvaYf+PXhYNHS20heBG7Rl2X6Zy8l11ZBWAHkWqE=
|
||||||
github.com/gorilla/feeds v1.0.0/go.mod h1:Nk0jZrvPFZX1OBe5NPiddPw7CfwF6Q9eqzaBbaightA=
|
github.com/gorilla/feeds v1.0.0/go.mod h1:Nk0jZrvPFZX1OBe5NPiddPw7CfwF6Q9eqzaBbaightA=
|
||||||
github.com/jtolds/qod v0.0.0-20170925014538-3abb44dfc7ba/go.mod h1:CC0XsLGDIrizqHTsUEJh89QHzv2u2mix/O9xl4+LqSw=
|
|
||||||
github.com/kardianos/osext v0.0.0-20170510131534-ae77be60afb1 h1:PJPDf8OUfOK1bb/NeTKd4f1QXZItOX389VN3B6qC8ro=
|
github.com/kardianos/osext v0.0.0-20170510131534-ae77be60afb1 h1:PJPDf8OUfOK1bb/NeTKd4f1QXZItOX389VN3B6qC8ro=
|
||||||
github.com/kardianos/osext v0.0.0-20170510131534-ae77be60afb1/go.mod h1:1NbS8ALrpOvjt0rHPNLyCIeMtbizbir8U//inJ+zuB8=
|
github.com/kardianos/osext v0.0.0-20170510131534-ae77be60afb1/go.mod h1:1NbS8ALrpOvjt0rHPNLyCIeMtbizbir8U//inJ+zuB8=
|
||||||
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
|
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
|
||||||
|
@ -19,10 +19,10 @@ github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORN
|
||||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||||
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||||
github.com/magefile/mage v0.0.0-20171025021237-ab3ca2f6f855/go.mod h1:IUDi13rsHje59lecXokTfGX0QIzO45uVPlXnJYsXepA=
|
|
||||||
github.com/pkg/errors v0.8.0 h1:WdK/asTD0HN+q6hsWO3/vpuAkAr+tw6aNJNDFFf0+qw=
|
github.com/pkg/errors v0.8.0 h1:WdK/asTD0HN+q6hsWO3/vpuAkAr+tw6aNJNDFFf0+qw=
|
||||||
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/russross/blackfriday v0.0.0-20170806171014-cadec560ec52 h1:xZ0R4UuR0EDx7m0OmvsqZaomfqCeYJ/PyLm94WLhnIM=
|
github.com/russross/blackfriday v0.0.0-20170806171014-cadec560ec52 h1:xZ0R4UuR0EDx7m0OmvsqZaomfqCeYJ/PyLm94WLhnIM=
|
||||||
github.com/russross/blackfriday v0.0.0-20170806171014-cadec560ec52/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
|
github.com/russross/blackfriday v0.0.0-20170806171014-cadec560ec52/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
|
||||||
github.com/segmentio/backo-go v0.0.0-20160424052352-204274ad699c h1:rsRTAcCR5CeNLkvgBVSjQoDGRRt6kggsE6XYBqCv2KQ=
|
github.com/segmentio/backo-go v0.0.0-20160424052352-204274ad699c h1:rsRTAcCR5CeNLkvgBVSjQoDGRRt6kggsE6XYBqCv2KQ=
|
||||||
|
|
|
@ -0,0 +1,43 @@
|
||||||
|
{
|
||||||
|
"header": {
|
||||||
|
"name": "Christine Dodrill",
|
||||||
|
"blog": "Blog",
|
||||||
|
"contact": "Contact",
|
||||||
|
"resume": "Resume"
|
||||||
|
},
|
||||||
|
"meta": {
|
||||||
|
"copyright": "Copyright %s Christine Dodrill. Any and all opinions listed here are my own, not my employer's.",
|
||||||
|
"rss_copyright": "This work is copyright Christine Dodrill. My viewpoints are my own and not the view of any employer past, current or future.",
|
||||||
|
"json_feed": "This is a JSON feed of my blogposts. For more information, read: https://jsonfeed.org/version/1"
|
||||||
|
},
|
||||||
|
"index": {
|
||||||
|
"contact_me": "Contact Me",
|
||||||
|
"title": "Web and Backend Services Devops Specialist",
|
||||||
|
"skills": "Skills",
|
||||||
|
"highlighted_projects": "Highlighted Projects"
|
||||||
|
},
|
||||||
|
"blog": {
|
||||||
|
"index_title": "Blogposts",
|
||||||
|
"blogroll": "Other Blogs I Find Interesting",
|
||||||
|
"description": "My blog posts and rants about various technology things.",
|
||||||
|
"title": "Christine Dodrill's Blog",
|
||||||
|
"posted_on": "Posted on %s",
|
||||||
|
"read_this": "Read Post",
|
||||||
|
"disclaimer": "Content posted on %s, opinions and preferences of the author may have changed since then."
|
||||||
|
},
|
||||||
|
"contact": {
|
||||||
|
"header": "Contact Information",
|
||||||
|
"email": "Email",
|
||||||
|
"social_media": "Social Media",
|
||||||
|
"other_info": "Other Information",
|
||||||
|
"donations": "To send me donations, my bitcoin address is: ",
|
||||||
|
"irc_comment": "I am on many IRC networks. On Freenode I am using the nick Xe but elsewhere I will use the nick Xena or Cadey."
|
||||||
|
},
|
||||||
|
"error": {
|
||||||
|
"title": "Error",
|
||||||
|
"post_not_found": "no such post found: %s"
|
||||||
|
},
|
||||||
|
"resume": {
|
||||||
|
"plaintext": "Plain-text version of this resume here"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,43 @@
|
||||||
|
{
|
||||||
|
"header": {
|
||||||
|
"name": "jan Wisi Totiwi",
|
||||||
|
"blog": "lipu sona",
|
||||||
|
"contact": "lipu toki",
|
||||||
|
"resume": "lipu pali mi"
|
||||||
|
},
|
||||||
|
"meta": {
|
||||||
|
"copyright": "lipu ni li pali e jan Wisi Totiwi pi suli %s. sina pali ala e ni. tenpo ale la nasin sona mi li nasin sona ala pi kulupu lawa mi.",
|
||||||
|
"rss_copyright": "lipu ni li pali e jan Wisi Totiwi. sina pali ala e ni. tenpo ale la nasin sona mi li nasin sona ala pi kulupu lawa mi.",
|
||||||
|
"json_feed": "lipu ni li lipu Sason Pe. sina wile sona la sina lukin e lipu: https://jsonfeed.org/version/1"
|
||||||
|
},
|
||||||
|
"index": {
|
||||||
|
"contact_me": "toki e mi",
|
||||||
|
"title": "jan pali e ilo suli",
|
||||||
|
"skills": "kepeken pali e mi",
|
||||||
|
"highlighted_projects": "pali musi e mi"
|
||||||
|
},
|
||||||
|
"blog": {
|
||||||
|
"index_title": "lipu sona",
|
||||||
|
"blogroll": "lipu sona pi jan ante",
|
||||||
|
"description": "lipu ni li jan Wisi Totiwi pi lipu sona. lipu sona ni li ilo pona li ilo sona.",
|
||||||
|
"title": "jan Wisi Totiwi pi lipu sona",
|
||||||
|
"posted_on": "tenpo pi %s",
|
||||||
|
"read_this": "lukin e ni",
|
||||||
|
"disclaimer": "tenpo %s la lipu ni li pali. tenpo ni la jan Wisi Totiwi li ken nasin sona ante."
|
||||||
|
},
|
||||||
|
"contact": {
|
||||||
|
"header": "lipu toki e mi",
|
||||||
|
"email": "ilo toki",
|
||||||
|
"social_media": "kalama ilo toki",
|
||||||
|
"other_info": "lipu ante",
|
||||||
|
"donations": "ken la sina pana mani e mi. sina wile kepeken lipu: ",
|
||||||
|
"irc_comment": "mi kepeken e kulupu mute pi ilo toki. nimi mi li 'Xe' anu 'Xena' anu 'Cadey'."
|
||||||
|
},
|
||||||
|
"error": {
|
||||||
|
"title": "pakala",
|
||||||
|
"post_not_found": "lipu %s li pona ala. ona li lukin ala."
|
||||||
|
},
|
||||||
|
"resume": {
|
||||||
|
"plaintext": "sina wile lipu pona la sina lukin e ni"
|
||||||
|
}
|
||||||
|
}
|
|
@ -56,11 +56,11 @@
|
||||||
{{ template "scripts" . }}
|
{{ template "scripts" . }}
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<header>
|
<header>
|
||||||
<p><a href="/">Christine Dodrill</a> - <a href="/blog">Blog</a> - <a href="/contact">Contact</a> - <a href="/resume">Resume</a></p>
|
<p><a href="/">{{ trans "header" "name" }}</a> - <a href="/blog">{{ trans "header" "blog" }}</a> - <a href="/contact">{{ trans "header" "contact" }}</a> - <a href="/resume">{{ trans "header" "resume" }}</a></p>
|
||||||
</header>
|
</header>
|
||||||
{{ template "content" . }}
|
{{ template "content" . }}
|
||||||
<footer>
|
<footer>
|
||||||
<blockquote>Copyright 2018 Christine Dodrill. Any and all opinions listed here are my own and not representative of my employer.</blockquote>
|
<blockquote>{{ trans "meta" "copyright" "2018" }}</blockquote>
|
||||||
</footer>
|
</footer>
|
||||||
<script>
|
<script>
|
||||||
if (navigator.serviceWorker.controller) {
|
if (navigator.serviceWorker.controller) {
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
{{ define "title" }}
|
{{ define "title" }}
|
||||||
<title>Blog - Christine Dodrill</title>
|
<title>{{ trans "header" "blog" }} - {{ trans "header" "name" }}</title>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.blogpost-card {
|
.blogpost-card {
|
||||||
|
@ -9,14 +9,14 @@
|
||||||
{{ end }}
|
{{ end }}
|
||||||
|
|
||||||
{{ define "content" }}
|
{{ define "content" }}
|
||||||
<h1>Blogposts</h1>
|
<h1>{{ trans "blog" "index_title" }}</h1>
|
||||||
|
|
||||||
<div class="grid">
|
<div class="grid">
|
||||||
{{ range . }}
|
{{ range . }}
|
||||||
<div class="card cell -4of12 blogpost-card">
|
<div class="card cell -4of12 blogpost-card">
|
||||||
<header class="card-header">{{ .Title }}</header>
|
<header class="card-header">{{ .Title }}</header>
|
||||||
<div class="card-content">
|
<div class="card-content">
|
||||||
<p>Posted on {{ .Date }} <br> <a href="{{ .Link }}">Read Post</a></p>
|
<p>{{ trans "blog" "posted_on" .Date }} <br> <a href="{{ .Link }}">{{ trans "blog" "read_this" }}</a></p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{{ end }}
|
{{ end }}
|
||||||
|
@ -24,7 +24,7 @@
|
||||||
|
|
||||||
<br />
|
<br />
|
||||||
|
|
||||||
<h2>Other Blogs I Find Interesting</h2>
|
<h2>{{ trans "blog" "blogroll" }}</h2>
|
||||||
|
|
||||||
<ul>
|
<ul>
|
||||||
<li><a href="https://write.as/excerpts/">Excerpts</a></li>
|
<li><a href="https://write.as/excerpts/">Excerpts</a></li>
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
{{ define "title" }}
|
{{ define "title" }}
|
||||||
<title>{{ .Title }} - Christine Dodrill</title>
|
<title>{{ .Title }} - {{ trans "header" "name" }}</title>
|
||||||
|
|
||||||
<!-- Twitter -->
|
<!-- Twitter -->
|
||||||
<meta name="twitter:card" content="summary" />
|
<meta name="twitter:card" content="summary" />
|
||||||
|
@ -10,11 +10,11 @@
|
||||||
<!-- Facebook -->
|
<!-- Facebook -->
|
||||||
<meta property="og:type" content="website" />
|
<meta property="og:type" content="website" />
|
||||||
<meta property="og:title" content="{{ .Title }}" />
|
<meta property="og:title" content="{{ .Title }}" />
|
||||||
<meta property="og:site_name" content="Christine Dodrill's Blog" />
|
<meta property="og:site_name" content="{{ trans "blog" "title" }}" />
|
||||||
|
|
||||||
<!-- Description -->
|
<!-- Description -->
|
||||||
<meta name="description" content="{{ .Title }} - Christine Dodrill's Blog" />
|
<meta name="description" content="{{ .Title }} - {{ trans "blog" "title" }}" />
|
||||||
<meta name="author" content="Christine Dodrill">
|
<meta name="author" content="{{ trans "header" "name" }}">
|
||||||
{{ end }}
|
{{ end }}
|
||||||
|
|
||||||
{{ define "content" }}
|
{{ define "content" }}
|
||||||
|
@ -22,5 +22,5 @@
|
||||||
|
|
||||||
<hr />
|
<hr />
|
||||||
|
|
||||||
<i>Content posted on {{ .Date }}, opinions and preferences of the author may have changed since then.</i>
|
<i>{{ trans "blog" "disclaimer" .Date }}</i>
|
||||||
{{ end }}
|
{{ end }}
|
||||||
|
|
|
@ -1,15 +1,13 @@
|
||||||
{{ define "title" }}<title>Contact - Christine Dodrill</title>{{ end }}
|
{{ define "title" }}<title>{{ trans "header" "contact" }} - {{ trans "header" "name" }}</title>{{ end }}
|
||||||
|
|
||||||
{{ define "content" }}
|
{{ define "content" }}
|
||||||
<h1>Contact Information</h1>
|
<h1>{{ trans "contact" "header" }}</h1>
|
||||||
<div class="grid">
|
<div class="grid">
|
||||||
<div class="cell -6of12">
|
<div class="cell -6of12">
|
||||||
<h3>Email</h3>
|
<h3>{{ trans "contact" "email" }}</h3>
|
||||||
<p>me@christine.website</p>
|
<p>me@christine.website</p>
|
||||||
|
|
||||||
<p>My GPG fingerprint is <code>799F 9134 8118 1111</code>. If you get an email that appears to be from me and the signature does not match that fingerprint, it is not from me. You may download a copy of my public key <a href="/static/gpg.pub">here</a>.</p>
|
<h3>{{ trans "contact" "social_media" }}</h3>
|
||||||
|
|
||||||
<h3>Social Media</h3>
|
|
||||||
<ul>
|
<ul>
|
||||||
<li><a href="https://github.com/Xe">Github</a></li>
|
<li><a href="https://github.com/Xe">Github</a></li>
|
||||||
<li><a href="https://twitter.com/theprincessxena">Twitter</a></li>
|
<li><a href="https://twitter.com/theprincessxena">Twitter</a></li>
|
||||||
|
@ -20,11 +18,11 @@
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<div class="cell -6of12">
|
<div class="cell -6of12">
|
||||||
<h3>Other Information</h3>
|
<h3>{{ trans "contact" "other_info" }}</h3>
|
||||||
<p>To send me donations, my bitcoin address is <code>1Gi2ZF2C9CU9QooH8bQMB2GJ2iL6shVnVe</code>.</p>
|
<p>{{ trans "contact" "donations" }} <code>1Gi2ZF2C9CU9QooH8bQMB2GJ2iL6shVnVe</code>.</p>
|
||||||
|
|
||||||
<h4>IRC</h4>
|
<h4>IRC</h4>
|
||||||
<p>I am on many IRC networks. On Freenode I am using the nick Xe but elsewhere I will use the nick Xena or Cadey.</p>
|
<p>{{ trans "contact" "irc_comment" }}</p>
|
||||||
|
|
||||||
<h4>Telegram</h4>
|
<h4>Telegram</h4>
|
||||||
<p><a href="https://t.me/miamorecadenza">@miamorecadenza</a></p>
|
<p><a href="https://t.me/miamorecadenza">@miamorecadenza</a></p>
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
{{ define "title" }}
|
{{ define "title" }}
|
||||||
<title>Error - Christine Dodrill</title>
|
<title>{{ trans "error" "title" }} - {{ trans "header" "name" }}</title>
|
||||||
{{ end }}
|
{{ end }}
|
||||||
|
|
||||||
{{ define "content" }}
|
{{ define "content" }}
|
||||||
|
|
|
@ -1,29 +1,36 @@
|
||||||
{{ define "title" }}<title>Christine Dodrill</title>{{ end }}
|
{{ define "title" }}<title>{{ trans "header" "name" }}</title>{{ end }}
|
||||||
|
|
||||||
{{ define "content" }}
|
{{ define "content" }}
|
||||||
<div class="grid">
|
<div class="grid">
|
||||||
<div class="cell -3of12 content">
|
<div class="cell -3of12 content">
|
||||||
<img src="/static/img/avatar.png">
|
<img src="/static/img/avatar.png">
|
||||||
<br />
|
<br />
|
||||||
<a href="/contact" class="justify-content-center">Contact Me</a>
|
<a href="/contact" class="justify-content-center">{{ trans "index" "contact_me" }}</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="cell -9of12 content">
|
<div class="cell -9of12 content">
|
||||||
<h1>Christine Dodrill</h1>
|
<h1>{{ trans "header" "name" }}</h1>
|
||||||
<h4>Web and Backend Services Devops Specialist</h4>
|
<h4>{{ trans "index" "title" }}</h4>
|
||||||
<h5>Skills</h5>
|
<h5>{{ trans "index" "skills" }}</h5>
|
||||||
<ul>
|
<ul>
|
||||||
<li>Go, Lua, Nim, Haskell, C, Python (3.x) and other languages</li>
|
<li>Go, Lua, Nim, Haskell, C, Python (3.x) and other languages</li>
|
||||||
<li>Docker (deployment, development & more)</li>
|
<li>Docker (deployment, development & more)</li>
|
||||||
<li>Mashups of data</li>
|
<li>Mashups of data</li>
|
||||||
<li>Package maintainer for Alpine Linux</li>
|
<li>Package maintainer for Alpine Linux</li>
|
||||||
|
<li>Speaks English, toki pona, and la .lojban.</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<h5>Highlighted Projects</h5>
|
<h5>{{ trans "index" "highlighted_projects" }}</h5>
|
||||||
<ul>
|
<ul>
|
||||||
<li><a href="https://github.com/Xe/PonyAPI">PonyAPI</a> - My Little Pony: Friendship is Magic Episode information API</li>
|
<li><a href="https://github.com/Xe/PonyAPI">PonyAPI</a> - My Little Pony: Friendship is Magic Episode information API</li>
|
||||||
<li><a href="https://github.com/PonyvilleFM/aura">Aura</a> - PonyvilleFM live DJ recording bot</li>
|
<li><a href="https://github.com/PonyvilleFM/aura">Aura</a> - PonyvilleFM live DJ recording bot</li>
|
||||||
<li><a href="https://github.com/Elemental-IRCd/elemental-ircd">Elemental-IRCd</a> - IRC Server Software</li>
|
<li><a href="https://github.com/Elemental-IRCd/elemental-ircd">Elemental-IRCd</a> - IRC Server Software</li>
|
||||||
<li><a href="https://github.com/Xe/site">This website</a> - The backend and templates for this website</li>
|
<li><a href="https://github.com/Xe/site">This website</a> - The backend and templates for this website</li>
|
||||||
|
<li><a href="https://github.com/Xe/olin">Olin</a> - WebAssembly on the server</li>
|
||||||
|
<li><a href="https://github.com/Xe/aports">aports</a> - Alpine Linux ports</li>
|
||||||
|
<li><a href="https://github.com/Xe/when-then-zen">when-then-zen</a> - Meditation instructions in Gherkin</li>
|
||||||
|
<li><a href="https://github.com/Xe/creators-code">Creator's Code</a> - Minimal code of conduct for communities</li>
|
||||||
|
<li><a href="https://github.com/Xe/printerfacts">printerfacts</a> - Informative facts about printers</li>
|
||||||
|
<li><a href="https://github.com/Xe/x">x</a> - Experiments and toys</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
{{ define "title" }}<title>Resume - Christine Dodrill</title>{{ end }}
|
{{ define "title" }}<title>{{ trans "header" "resume" }} - {{ trans "header" "name" }}</title>{{ end }}
|
||||||
|
|
||||||
{{ define "content" }}
|
{{ define "content" }}
|
||||||
{{ . }}
|
{{ . }}
|
||||||
|
|
||||||
<hr />
|
<hr />
|
||||||
|
|
||||||
<a href="/static/resume/resume.md">Plain-text version of this resume here</a>
|
<a href="/static/resume/resume.md">{{ trans "resume" "plaintext" }}</a>
|
||||||
{{ end }}
|
{{ end }}
|
||||||
|
|
Loading…
Reference in New Issue