func (l *I18n) LoadFile(lang string, file string) (err error) {
	if ld, ok := l.data[lang]; ok {
		err = ld.LoadFiles(file)
		if err != nil {
			return
		}
	} else {
		err = errors.New("language" + lang + " not exist, please create it before load data")
	}
	return
}
LoadFile append data to a exist language
Usage:
i18n.LoadFile("zh-CN", "path/to/zh-CN.ini")
package i18n
import (
	"bytes"
	"fmt"
	"os"
	"strconv"
	"strings"
	"github.com/abulo/ratel/v2/config/parser/ini"
	"github.com/pkg/errors"
)
const (
	FileMode uint8 = 0
	DirMode uint8 = 1
	SprintfMode uint8 = 0
	ReplaceMode uint8 = 1
)
type I18n struct {
	data map[string]*ini.Ini
	langDir string
	languages map[string]string
	LoadMode uint8
	TransMode uint8
	DefaultLang string
	FallbackLang string
}
var defI18n = NewEmpty()
func Default() *I18n {
	return defI18n
}
func T(lang string, key string, args ...interface{}) string {
	return defI18n.T(lang, key, args...)
}
func Tr(lang string, key string, args ...interface{}) string {
	return defI18n.Tr(lang, key, args...)
}
func Dt(key string, args ...interface{}) string {
	return defI18n.DefTr(key, args...)
}
func DefTr(key string, args ...interface{}) string {
	return defI18n.DefTr(key, args...)
}
func Init(langDir string, defLang string, languages map[string]string) *I18n {
	defI18n.langDir = langDir
	defI18n.languages = languages
	defI18n.DefaultLang = defLang
	return defI18n.Init()
}
func New(langDir string, defLang string, languages map[string]string) *I18n {
	return &I18n{
		data: make(map[string]*ini.Ini),
		langDir:   langDir,
		languages: languages,
		DefaultLang: defLang,
	}
}
func NewEmpty() *I18n {
	return &I18n{
		data: make(map[string]*ini.Ini),
		languages: make(map[string]string),
	}
}
func NewWithInit(langDir string, defLang string, languages map[string]string) *I18n {
	m := New(langDir, defLang, languages)
	return m.Init()
}
func (l *I18n) Dt(key string, args ...interface{}) string {
	return l.Tr(l.DefaultLang, key, args...)
}
func (l *I18n) DefTr(key string, args ...interface{}) string {
	return l.Tr(l.DefaultLang, key, args...)
}
func (l *I18n) T(lang, key string, args ...interface{}) string {
	return l.Tr(lang, key, args...)
}
func (l *I18n) Tr(lang, key string, args ...interface{}) string {
	if !l.HasLang(lang) {
		msg := l.transFromFallback(key)
		if msg == "" {
			return key
		}
		if len(args) > 0 {
			msg = l.renderMessage(msg, args...)
		}
		return msg
	}
	msg, ok := l.data[lang].GetValue(key)
	if !ok {
		msg = l.transFromFallback(key)
	}
	if msg == "" {
		return key
	}
	if len(args) > 0 {
		msg = l.renderMessage(msg, args...)
	}
	return msg
}
func (l *I18n) HasKey(lang, key string) (ok bool) {
	if !l.HasLang(lang) {
		return
	}
	_, ok = l.data[lang].GetValue(key)
	return
}
func (l *I18n) transFromFallback(key string) string {
	fl := l.FallbackLang
	if !l.HasLang(fl) {
		return ""
	}
	return l.data[fl].String(key)
}
func (l *I18n) renderMessage(msg string, args ...interface{}) string {
	if l.TransMode == SprintfMode {
		return fmt.Sprintf(msg, args...)
	}
	if ss, ok := args[0].([]string); ok {
		return strings.NewReplacer(ss...).Replace(msg)
	}
	var ss []string
	if mp, ok := args[0].(map[string]interface{}); ok {
		for k, v := range mp {
			ss = append(ss, "{"+k+"}")
			ss = append(ss, toString(v))
		}
	} else {
		for i, val := range args {
			str := toString(val)
			if i%2 == 0 {
				str = "{" + str + "}"
			}
			ss = append(ss, str)
		}
	}
	return strings.NewReplacer(ss...).Replace(msg)
}
func toString(val interface{}) (str string) {
	switch tVal := val.(type) {
	case int:
		str = strconv.Itoa(tVal)
	case int8:
		str = strconv.Itoa(int(tVal))
	case int16:
		str = strconv.Itoa(int(tVal))
	case int32:
		str = strconv.Itoa(int(tVal))
	case int64:
		str = strconv.Itoa(int(tVal))
	case uint:
		str = strconv.Itoa(int(tVal))
	case uint8:
		str = strconv.Itoa(int(tVal))
	case uint16:
		str = strconv.Itoa(int(tVal))
	case uint32:
		str = strconv.Itoa(int(tVal))
	case uint64:
		str = strconv.Itoa(int(tVal))
	case float32:
		str = fmt.Sprint(tVal)
	case float64:
		str = fmt.Sprint(tVal)
	case string:
		str = tVal
	case nil:
		str = ""
	default:
		str = "CANNOT-TO-STRING"
	}
	return
}
func (l *I18n) Init() *I18n {
	if l.LoadMode == FileMode {
		l.loadSingleFiles()
	} else if l.LoadMode == DirMode {
		l.loadDirFiles()
	} else {
		panic("invalid load mode setting. only allow 0, 1")
	}
	return l
}
func (l *I18n) loadSingleFiles() {
	pathSep := string(os.PathSeparator)
	for lang := range l.languages {
		lData := ini.New()
		err := lData.LoadFiles(l.langDir + pathSep + lang + ".ini")
		if err != nil {
			panic("fail to load language: " + lang + ", error " + err.Error())
		}
		l.data[lang] = lData
	}
}
func (l *I18n) loadDirFiles() {
	pathSep := string(os.PathSeparator)
	for lang := range l.languages {
		dirPath := l.langDir + pathSep + lang
		files, err := os.ReadDir(dirPath)
		if err != nil {
			panic("read dir fail: " + dirPath + ", error " + err.Error())
		}
		sl := l.data[lang]
		for _, fi := range files {
			if fi.IsDir() || !strings.HasSuffix(fi.Name(), ".ini") {
				continue
			}
			var err error
			if sl != nil {
				err = sl.LoadFiles(dirPath + pathSep + fi.Name())
			} else {  
				sl = ini.New()
				err = sl.LoadFiles(dirPath + pathSep + fi.Name())
				l.data[lang] = sl
			}
			if err != nil {
				panic("fail to load language file: " + lang + ", error " + err.Error())
			}
		}
	}
}
func (l *I18n) Add(lang string, name string) {
	l.NewLang(lang, name)
}
func (l *I18n) NewLang(lang string, name string) {
	if _, ok := l.languages[lang]; ok {
		return
	}
	l.data[lang] = ini.New()
	l.languages[lang] = name
}