Skip to content

Commit

Permalink
language package
Browse files Browse the repository at this point in the history
  • Loading branch information
strider2038 committed Feb 26, 2021
1 parent f7473aa commit 4ba9cbe
Show file tree
Hide file tree
Showing 9 changed files with 448 additions and 2 deletions.
31 changes: 31 additions & 0 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
name: CI

on:
push:
branches: [ '*' ]
pull_request:
branches: [ '*' ]

jobs:
test:
runs-on: ubuntu-latest
steps:
- name: Set up Go
uses: actions/setup-go@v2
with:
go-version: ^1.15
id: go

- name: Checkout code
uses: actions/checkout@v2

- name: Set up dependencies
run: go mod download

- name: Run golangci-lint
uses: golangci/golangci-lint-action@v2
with:
version: v1.36

- name: Run tests
run: go test -v $(go list ./... | grep -v vendor)
68 changes: 68 additions & 0 deletions .golangci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
linters:
enable:
- asciicheck
- bodyclose
- deadcode
- depguard
- dogsled
- dupl
- errcheck
- exhaustive
- forbidigo
- funlen
- gochecknoinits
- gocognit
- goconst
- gocritic
- gocyclo
- godot
- godox
- goerr113
- gofmt
- goimports
- golint
- gomodguard
- goprintffuncname
- gosec
- gosimple
- govet
- ineffassign
- interfacer
- lll
- makezero
- maligned
- misspell
- nakedret
- nestif
- noctx
- nolintlint
- prealloc
- rowserrcheck
- scopelint
- staticcheck
- structcheck
- stylecheck
- thelper
- typecheck
- unconvert
- unparam
- unused
- varcheck
- whitespace

issues:
exclude-rules:
# Exclude some linters from running on tests files.
- path: _test\.go
linters:
- dogsled
- dupl
- errcheck
- forbidigo
- funlen
- gochecknoglobals
- gocyclo
- goerr113
- gosec
- lll
- scopelint
71 changes: 69 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,69 @@
# language
Language HTTP Middleware
# Language package for Golang

Package language provides HTTP middleware for parsing language from HTTP request and passing it via context.

## Example of reading language from Accept-Language header

```golang
package main

import (
"fmt"
"net/http"
"net/http/httptest"

"golang.org/x/text/language"

languagepkg "github.com/muonsoft/language"
)

func main() {
h := http.HandlerFunc(func (writer http.ResponseWriter, request *http.Request) {
tag := languagepkg.FromContext(request.Context())
fmt.Println("language:", tag)
})
m := languagepkg.NewMiddleware(h, languagepkg.SupportedLanguages(language.English, language.Russian))

r := httptest.NewRequest(http.MethodGet, "/", nil)
r.Header.Set("Accept-Language", "ru")
w := httptest.NewRecorder()

m.ServeHTTP(w, r)
// Output: language: ru
}
```

## Example of reading language from Cookie

```golang
package main

import (
"fmt"
"net/http"
"net/http/httptest"

"golang.org/x/text/language"

languagepkg "github.com/muonsoft/language"
)

func main() {
h := http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) {
tag := languagepkg.FromContext(request.Context())
fmt.Println("language:", tag)
})
m := language.NewMiddleware(
h,
languagepkg.SupportedLanguages(language.English, language.Russian),
languagepkg.ReadFromCookie("lang"),
)

r := httptest.NewRequest(http.MethodGet, "/", nil)
r.AddCookie(&http.Cookie{Name: "lang", Value: "ru"})
w := httptest.NewRecorder()

m.ServeHTTP(w, r)
// Output: language: ru
}
```
23 changes: 23 additions & 0 deletions context.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package language

import (
"context"

"golang.org/x/text/language"
)

type contextKey string

const languageKey contextKey = "language"

// FromContext returns language tag. If language tag does not exist it returns language.Und value.
func FromContext(ctx context.Context) language.Tag {
tag, _ := ctx.Value(languageKey).(language.Tag)

return tag
}

// WithContext adds language tag to context.
func WithContext(ctx context.Context, tag language.Tag) context.Context {
return context.WithValue(ctx, languageKey, tag)
}
3 changes: 3 additions & 0 deletions doc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
// Package language provides HTTP middleware for parsing language from HTTP request and
// passing it via context.
package language
5 changes: 5 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
module github.com/muonsoft/language

go 1.13

require golang.org/x/text v0.3.5
3 changes: 3 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
golang.org/x/text v0.3.5 h1:i6eZZ+zk0SOf0xgBpEpPD18qWcJda6q1sxt3S0kzyUQ=
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
82 changes: 82 additions & 0 deletions middleware.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
package language

import (
"net/http"

"golang.org/x/text/language"
)

// Middleware is used to parse language from Accept-Language header or custom cookie from
// from current request and pass best matching language via context to next http.Handler.
type Middleware struct {
matcher language.Matcher
readers []func(r *http.Request) string
next http.Handler
}

type MiddlewareOption func(middleware *Middleware)

// ReadFromCookie can be used to set up middleware to read language value from cookie with given name.
func ReadFromCookie(name string) MiddlewareOption {
return func(middleware *Middleware) {
middleware.readers = append(middleware.readers, func(r *http.Request) string {
cookie, _ := r.Cookie(name)
return cookie.Value
})
}
}

// ReadFromAcceptHeader can be used to set up middleware to read language value from Accept-Language header.
func ReadFromAcceptHeader() MiddlewareOption {
return func(middleware *Middleware) {
middleware.readers = append(middleware.readers, readFromAcceptLanguageHeader)
}
}

// SupportedLanguages is used to set up list of supported languages. See language.NewMatcher() for details.
func SupportedLanguages(tags ...language.Tag) MiddlewareOption {
return func(middleware *Middleware) {
middleware.matcher = language.NewMatcher(tags)
}
}

// NewMiddleware creates middleware for parsing language from Accept-Language header or a cookie
// and passing its value via context.
//
// By default Middleware uses only English language and Accept-Language header as source.
//
// To set up supported languages list use SupportedLanguages option.
//
// To set up sources of language value use ReadFromCookie and ReadFromAcceptHeader options. Order of
// sources should be preserved.
func NewMiddleware(next http.Handler, options ...MiddlewareOption) *Middleware {
middleware := &Middleware{next: next}

for _, setOption := range options {
setOption(middleware)
}
if middleware.matcher == nil {
middleware.matcher = language.NewMatcher([]language.Tag{language.English})
}
if len(middleware.readers) == 0 {
middleware.readers = append(middleware.readers, readFromAcceptLanguageHeader)
}

return middleware
}

func (middleware *Middleware) ServeHTTP(writer http.ResponseWriter, request *http.Request) {
sources := make([]string, 0, len(middleware.readers))
for _, readLanguage := range middleware.readers {
sources = append(sources, readLanguage(request))
}

tag, _ := language.MatchStrings(middleware.matcher, sources...)
ctx := WithContext(request.Context(), tag)

middleware.next.ServeHTTP(writer, request.WithContext(ctx))
}

func readFromAcceptLanguageHeader(r *http.Request) string {
return r.Header.Get("Accept-Language")
}
Loading

0 comments on commit 4ba9cbe

Please sign in to comment.