func loadMessages(locale string, messagesPaths []string) (messages map[string]string, errors []error) {
	messages = make(map[string]string)
	found := false
	for _, p := range messagesPaths {
		p = strings.TrimRight(p, pathSeparator)
		file := p + pathSeparator + locale + ".yaml"
		_, statErr := os.Stat(file)
		if statErr == nil {
			contents, readErr := ioutil.ReadFile(file)
			if readErr != nil {
				errors = append(errors, translatorError{message: "can't open messages file: " + readErr.Error()})
			} else {
				newmap := map[string]string{}
				yamlErr := yaml.Unmarshal(contents, &newmap)
				if yamlErr != nil {
					errors = append(errors, translatorError{message: "can't load messages YAML: " + yamlErr.Error()})
				} else {
					found = true
					for key, value := range newmap {
						messages[key] = value
					}
				}
			}
		}
		dir := p + pathSeparator + locale
		info, statErr := os.Stat(dir)
		if statErr == nil && info.IsDir() {
			files, globErr := filepath.Glob(dir + pathSeparator + "*.yaml")
			if globErr != nil {
				errors = append(errors, translatorError{message: "can't glob messages files: " + globErr.Error()})
			}
			for _, file := range files {
				contents, readErr := ioutil.ReadFile(file)
				if readErr != nil {
					errors = append(errors, translatorError{message: "can't open messages file: " + readErr.Error()})
				} else {
					newmap := map[string]string{}
					yamlErr := yaml.Unmarshal(contents, &newmap)
					if yamlErr != nil {
						errors = append(errors, translatorError{message: "can't load messages YAML: " + yamlErr.Error()})
					} else {
						found = true
						for key, value := range newmap {
							messages[key] = value
						}
					}
				}
			}
		}
	}
	if !found {
		errors = append(errors, translatorError{message: "no messages files found: " + locale})
	}
	return
}
loadMessages loads all messages from the properly named locale message yaml
files in the requested messagesPaths.  if multiple paths are provided, paths
further down the list take precedence over earlier paths.
package i18n
import (
	"io/ioutil"
	"os"
	"path"
	"path/filepath"
	"strings"
	"gopkg.in/yaml.v1"
)
type TranslatorFactory struct {
	messagesPaths []string
	rulesPaths    []string
	translators   map[string]*Translator
	fallback      *Translator
}
type Translator struct {
	messages map[string]string
	locale   string
	rules    *TranslatorRules
	fallback *Translator
}
type translatorError struct {
	translator *Translator
	message    string
}
var pathSeparator string
func init() {
	p := path.Join("a", "b")
	pathSeparator = p[1 : len(p)-1]
}
func (e translatorError) Error() string {
	if e.translator != nil {
		return "translator error (locale: " + e.translator.locale + ") - " + e.message
	}
	return "translator error - " + e.message
}
func NewTranslatorFactory(rulesPaths []string, messagesPaths []string, fallbackLocale string) (f *TranslatorFactory, errors []error) {
	f = new(TranslatorFactory)
	if len(rulesPaths) == 0 {
		errors = append(errors, translatorError{message: "rules paths empty"})
	}
	if len(messagesPaths) == 0 {
		errors = append(errors, translatorError{message: "messages paths empty"})
	}
	foundRules := fallbackLocale == ""
	foundMessages := fallbackLocale == ""
	for _, p := range rulesPaths {
		p = strings.TrimRight(p, pathSeparator)
		_, err := os.Stat(p)
		if err != nil {
			errors = append(errors, translatorError{message: "can't read rules path " + p + ": " + err.Error()})
		}
		if !foundRules {
			_, err = os.Stat(p + pathSeparator + fallbackLocale + ".yaml")
			if err == nil {
				foundRules = true
			}
		}
	}
	for _, p := range messagesPaths {
		p = strings.TrimRight(p, pathSeparator)
		_, err := os.Stat(p)
		if err != nil {
			errors = append(errors, translatorError{message: "can't read messages path " + p + ": " + err.Error()})
		}
		if !foundMessages {
			_, err = os.Stat(p + pathSeparator + fallbackLocale + ".yaml")
			if err == nil {
				foundMessages = true
			} else {
				info, err := os.Stat(p + pathSeparator + fallbackLocale)
				if err == nil && info.IsDir() {
					files, _ := filepath.Glob(p + pathSeparator + fallbackLocale + pathSeparator + "*.yaml")
					for _, file := range files {
						_, err = os.Stat(file)
						if err == nil {
							foundMessages = true
							break
						}
					}
				}
			}
		}
	}
	if !foundRules {
		errors = append(errors, translatorError{message: "found no rules for fallback locale"})
	}
	if !foundMessages {
		errors = append(errors, translatorError{message: "found no messages for fallback locale"})
	}
	f.rulesPaths = rulesPaths
	f.messagesPaths = messagesPaths
	f.translators = map[string]*Translator{}
	if fallbackLocale != "" {
		var errs []error
		f.fallback, errs = f.GetTranslator(fallbackLocale)
		for _, err := range errs {
			errors = append(errors, err)
		}
	}
	return
}
func (f *TranslatorFactory) GetTranslator(localeCode string) (t *Translator, errors []error) {
	fallback := f.getFallback(localeCode)
	if t, ok := f.translators[localeCode]; ok {
		return t, nil
	}
	exists, errs := f.LocaleExists(localeCode)
	if !exists {
		errors = append(errors, translatorError{message: "could not find rules and messages for locale " + localeCode})
	}
	for _, e := range errs {
		errors = append(errors, e)
	}
	rules := new(TranslatorRules)
	files := []string{}
	for _, p := range f.rulesPaths {
		p = strings.TrimRight(p, pathSeparator)
		files = append(files, p+pathSeparator+"root.yaml")
	}
	parts := strings.Split(localeCode, "-")
	if len(parts) > 1 {
		for i, _ := range parts {
			fb := strings.Join(parts[0:i+1], "-")
			for _, p := range f.rulesPaths {
				p = strings.TrimRight(p, pathSeparator)
				files = append(files, p+pathSeparator+fb+".yaml")
			}
		}
	}
	for _, p := range f.rulesPaths {
		p = strings.TrimRight(p, pathSeparator)
		files = append(files, p+pathSeparator+localeCode+".yaml")
	}
	errs = rules.load(files)
	for _, err := range errs {
		errors = append(errors, err)
	}
	messages, errs := loadMessages(localeCode, f.messagesPaths)
	for _, err := range errs {
		errors = append(errors, err)
	}
	t = new(Translator)
	t.locale = localeCode
	t.messages = messages
	t.fallback = fallback
	t.rules = rules
	f.translators[localeCode] = t
	return
}
func (f *TranslatorFactory) getFallback(localeCode string) *Translator {
	if f.fallback != nil && localeCode == f.fallback.locale {
		return nil
	}
	separator := "-"
	fallback := f.fallback
	parts := strings.Split(localeCode, separator)
	for len(parts) > 1 {
		parts = parts[0 : len(parts)-1]
		fb := strings.Join(parts, separator)
		if exists, _ := f.LocaleExists(fb); exists {
			fallback, _ = f.GetTranslator(fb)
			break
		}
	}
	return fallback
}
func (f *TranslatorFactory) LocaleExists(localeCode string) (exists bool, errs []error) {
	for _, p := range f.messagesPaths {
		p = strings.TrimRight(p, pathSeparator)
		_, err := os.Stat(p + pathSeparator + localeCode + ".yaml")
		if err == nil {
			exists = true
			return
		} else if !os.IsNotExist(err) {
			errs = append(errs, translatorError{message: "error getting file info: " + err.Error()})
		}
		info, err := os.Stat(p + pathSeparator + localeCode)
		if err == nil && info.IsDir() {
			files, _ := filepath.Glob(p + pathSeparator + localeCode + pathSeparator + "*.yaml")
			for _, file := range files {
				_, err := os.Stat(file)
				if err == nil {
					exists = true
					return
				} else if !os.IsNotExist(err) {
					errs = append(errs, translatorError{message: "error getting file info: " + err.Error()})
				}
			}
		}
	}
	return
}
func (t *Translator) Translate(key string, substitutions map[string]string) (translation string, errors []error) {
	if _, ok := t.messages[key]; !ok {
		if t.fallback != nil && t.fallback != t {
			return t.fallback.Translate(key, substitutions)
		}
		errors = append(errors, translatorError{translator: t, message: "key not found: " + key})
		return
	}
	translation, errors = t.substitute(t.messages[key], substitutions)
	return
}
func (t *Translator) Pluralize(key string, number float64, numberStr string) (translation string, errors []error) {
	if _, ok := t.messages[key]; !ok {
		if t.fallback != nil && t.fallback != t {
			return t.fallback.Pluralize(key, number, numberStr)
		}
		errors = append(errors, translatorError{translator: t, message: "key not found: " + key})
		return
	}
	form := (t.rules.PluralRuleFunc)(number)
	parts := strings.Split(t.messages[key], "|")
	if form > len(parts)-1 {
		errors = append(errors, translatorError{translator: t, message: "too few plural variations: " + key})
		form = len(parts) - 1
	}
	var errs []error
	translation, errs = t.substitute(parts[form], map[string]string{"n": numberStr})
	for _, err := range errs {
		errors = append(errors, err)
	}
	return
}
func (t *Translator) Rules() TranslatorRules {
	rules := *t.rules
	return rules
}
func (t *Translator) Direction() (direction string) {
	return t.rules.Direction
}
func (t *Translator) substitute(str string, substitutions map[string]string) (substituted string, errors []error) {
	substituted = str
	for find, replace := range substitutions {
		if strings.Index(str, "{"+find+"}") == -1 {
			errors = append(errors, translatorError{translator: t, message: "substitution not found: " + str + ", " + replace})
		}
		substituted = strings.Replace(substituted, "{"+find+"}", replace, -1)
	}
	return
}