-
Notifications
You must be signed in to change notification settings - Fork 0
/
match.go
170 lines (140 loc) · 3.79 KB
/
match.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
package i18n
import (
"fmt"
"net/http"
"strings"
"github.com/ThatsMrTalbot/scaffold"
"golang.org/x/net/context"
"golang.org/x/text/language"
)
var (
languages = newRequestLanguageMap()
)
// Matcher get parses languages from http requests
type Matcher struct {
i18n *I18n
}
// NewMatcher creates a new matcher
func NewMatcher(i18n *I18n) *Matcher {
return &Matcher{
i18n: i18n,
}
}
func (matcher *Matcher) stripPrefix(prefix string, r *http.Request) {
if p := strings.TrimPrefix(r.URL.Path, prefix); len(p) < len(r.URL.Path) {
r.URL.Path = p
}
}
// Middleware returns scaffold.Middleware that stores the language in the context
// redirecting if necessary
func (matcher *Matcher) Middleware() scaffold.Middleware {
return scaffold.Middleware(func(next scaffold.Handler) scaffold.Handler {
return scaffold.HandlerFunc(func(ctx context.Context, w http.ResponseWriter, r *http.Request) {
if tag, redirect := matcher.handle(w, r); !redirect {
ctx = NewLanguageContext(ctx, tag)
prefix := fmt.Sprintf("/%s", matcher.tagString(tag))
matcher.stripPrefix(prefix, r)
next.CtxServeHTTP(ctx, w, r)
}
})
})
}
// NewLanguageContext stores a language tag in the context
func NewLanguageContext(ctx context.Context, tag language.Tag) context.Context {
return context.WithValue(ctx, "i18n_tag", tag)
}
// GetLanguageFromContext returns the language from the context
func GetLanguageFromContext(ctx context.Context) language.Tag {
if tag, ok := ctx.Value("i18n_tag").(language.Tag); ok {
return tag
}
return language.Und
}
// Wrapper returns a http.Handler that stores languages in a sharded map for
// retrieval using GetLanguageFromRequest
func (matcher *Matcher) Wrapper(handler http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if tag, redirect := matcher.handle(w, r); !redirect {
languages.Add(r, tag)
defer languages.Delete(r)
if handler != nil {
prefix := fmt.Sprintf("/%s", matcher.tagString(tag))
matcher.stripPrefix(prefix, r)
handler.ServeHTTP(w, r)
}
http.NotFound(w, r)
}
})
}
// GetLanguageFromRequest gets the language from the internal map
func GetLanguageFromRequest(r *http.Request) language.Tag {
if tag, ok := languages.Get(r); ok {
return tag
}
return language.Und
}
func (matcher *Matcher) match(lang string) (matched language.Tag, match bool, valid bool, exact bool) {
tag, err := language.Parse(lang)
if err != nil || lang == "" {
return language.Und, false, false, false
}
matched = language.Und
exact = true
match = false
valid = true
for {
for _, supported := range matcher.i18n.GetSupportedLanguages() {
if supported.String() == tag.String() {
matched = supported
match = true
return
}
}
exact = false
if tag.IsRoot() {
break
}
tag = tag.Parent()
}
return
}
func (matcher *Matcher) handle(w http.ResponseWriter, r *http.Request) (language.Tag, bool) {
path := r.URL.Path
segments := strings.Split(path, "/")
if len(segments) <= 1 {
segments = []string{"", ""}
}
tag, match, valid, exact := matcher.match(segments[1])
if !match {
tags, _, _ := language.ParseAcceptLanguage(r.Header.Get("Accept-Language"))
tag = matcher.i18n.GetDefaultLanguage()
for _, t := range tags {
a, b, _, c := matcher.match(t.String())
if b {
tag, match, exact = a, b, c
break
}
}
}
if !valid {
str := matcher.tagString(tag)
segments = append([]string{"", str}, segments[1:]...)
}
if !exact {
str := matcher.tagString(tag)
segments[1] = str
}
if !match || !valid || !exact {
r.URL.Path = strings.Join(segments, "/")
http.Redirect(w, r, r.URL.String(), 302)
return tag, true
}
return tag, false
}
func (matcher *Matcher) tagString(tag language.Tag) string {
str := tag.String()
if str == "und" {
return ""
}
return str
}