localization.go 2.91 KB
package webutility

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net/http"
	"path"
	"strconv"
	"strings"
	"sync"
)

// Dictionary ...
type Dictionary struct {
	my            sync.Mutex
	locales       map[string]map[string]string
	supported     []string
	defaultLocale string
}

// NewDictionary ...
func NewDictionary() *Dictionary {
	return &Dictionary{
		locales: map[string]map[string]string{},
	}
}

// AddTranslations ...
func (d *Dictionary) AddTranslations(directory string) error {
	files, err := ioutil.ReadDir(directory)
	if err != nil {
		return err
	}

	for _, fileInfo := range files {
		fName := fileInfo.Name()
		path := directory + "/" + fName
		file, err := ioutil.ReadFile(path)
		if err != nil {
			return err
		}

		loc := stripFileExtension(fName)

		var data interface{}
		err = json.Unmarshal(file, &data)
		if err != nil {
			return err
		}

		l := map[string]string{}
		for k, v := range data.(map[string]interface{}) {
			l[k] = v.(string)
		}

		mu.Lock()
		defer mu.Unlock()
		d.locales[loc] = l
		d.supported = append(d.supported, loc)
	}

	if d.defaultLocale == "" && len(d.supported) > 0 {
		d.defaultLocale = d.supported[0]
	}

	return nil
}

// GetBestMatchLocale ...
func (d *Dictionary) GetBestMatchLocale(req *http.Request) (best string) {
	accepted := d.parseAcceptedLanguages(req.Header.Get("Accept-Language"))

	for i := range accepted {
		if accepted[i].Code == "*" {
			return d.defaultLocale
		}
		for j := range d.supported {
			if accepted[i].Code == d.supported[j] {
				return d.supported[j]
			}
		}
	}

	return d.defaultLocale
}

// Translate ...
func (d *Dictionary) Translate(loc, key string) string {
	return d.locales[loc][key]
}

// SetDefaultLocale ...
func (d *Dictionary) SetDefaultLocale(loc string) error {
	if !d.contains(loc) {
		return fmt.Errorf("locale file not loaded: %s", loc)
	}

	d.defaultLocale = loc

	return nil
}

func (d *Dictionary) contains(loc string) bool {
	for _, v := range d.supported {
		if v == loc {
			return true
		}
	}
	return false
}

// LangWeight ...
type LangWeight struct {
	Code   string
	Weight float64
}

func (d *Dictionary) parseAcceptedLanguages(accepted string) (langs []LangWeight) {
	if accepted == "" {
		langs = append(langs, LangWeight{Code: d.defaultLocale, Weight: 1.0})
		return langs
	}

	var code string
	var weight float64

	parts := strings.Split(accepted, ",")
	for i := range parts {
		parts[i] = strings.Trim(parts[i], " ")

		cw := strings.Split(parts[i], ";")
		paramCount := len(cw)

		if paramCount == 1 {
			// default weight of 1
			code = cw[0]
			weight = 1.0
		} else if paramCount == 2 {
			// parse weight
			code = cw[0]
			weight, _ = strconv.ParseFloat(cw[1][2:], 64)

		}

		langs = append(langs, LangWeight{Code: code, Weight: weight})
	}

	// TODO(marko): sort langs by weights?

	return langs
}

func stripFileExtension(full string) (stripped string) {
	extension := path.Ext(full)
	stripped = strings.TrimSuffix(full, extension)
	return stripped
}