├── README.md ├── i18n.go └── locales ├── en.i10n ├── zh-CN.i10n └── zh-TW.i10n /README.md: -------------------------------------------------------------------------------- 1 | go-i18n 2 | ======= 3 | 4 | collect locale data for use in golang -------------------------------------------------------------------------------- /i18n.go: -------------------------------------------------------------------------------- 1 | package i18n 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "strings" 7 | "time" 8 | ) 9 | 10 | var ( 11 | // ErrInvalidLocale is returned if a specified locale is invalid. 12 | ErrInvalidLocale = errors.New("invalid locale") 13 | ) 14 | 15 | type IL struct { 16 | defaultLocale string 17 | dirPath string 18 | locales []string 19 | locale2SourceKey2Trans map[string]map[string]string 20 | } 21 | 22 | func NewIL(translationsDirPath, locale string) (*IL, error) { 23 | locales := localesChainForLocale(locale) 24 | if len(locales) == 0 { 25 | return nil, ErrInvalidLocale 26 | } 27 | 28 | il := &Dict{ 29 | defaultLocale: locales[0], 30 | dirPath: translationsDirPath, 31 | locales: locales, 32 | locale2SourceKey2Trans: make(map[string]map[string]string), 33 | } 34 | 35 | if err := il.loadDefaultTranslations(locales); err != nil { 36 | return nil, err 37 | } 38 | 39 | if err := il.loadTranslations(translationsDirPath); err != nil { 40 | return nil, err 41 | } 42 | 43 | return il, nil 44 | } 45 | 46 | func (il *IL) Translate(key string) string { 47 | 48 | } 49 | 50 | func (il *IL) Time(t *time.Time, args ...interface{}) string { 51 | 52 | } 53 | 54 | func (il *IL) Money(key int64) string { 55 | 56 | } 57 | 58 | func (il *IL) loadDefaultTranslations(locales []string) { 59 | 60 | } 61 | 62 | // DirPath returns the translations directory path of the international. 63 | func (il *IL) DirPath() string { 64 | return il.dirPath 65 | } 66 | 67 | // Locale returns the locale of the international. 68 | func (il *IL) Locale() string { 69 | return il.locales[0] 70 | } 71 | 72 | // Translation returns a translation of the source string to the locale of the 73 | // Dict or the source string, if no matching translation was found. 74 | // 75 | // Provided context arguments are used for disambiguation. 76 | func (il *IL) Translation(source string, context ...string) string { 77 | for _, locale := range il.locales { 78 | if sourceKey2Trans, ok := il.locale2SourceKey2Trans[locale]; ok { 79 | if trans, ok := sourceKey2Trans[sourceKey(source, context)]; ok { 80 | return trans 81 | } 82 | } 83 | } 84 | 85 | return source 86 | } 87 | 88 | func (il *IL) loadTranslation(reader io.Reader, locale string) error { 89 | var trf trfile 90 | 91 | if err := json.NewDecoder(reader).Decode(&trf); err != nil { 92 | return err 93 | } 94 | 95 | sourceKey2Trans, ok := il.locale2SourceKey2Trans[locale] 96 | if !ok { 97 | sourceKey2Trans = make(map[string]string) 98 | 99 | il.locale2SourceKey2Trans[locale] = sourceKey2Trans 100 | } 101 | 102 | for _, m := range trf.Messages { 103 | if m.Translation != "" { 104 | sourceKey2Trans[sourceKey(m.Source, m.Context)] = m.Translation 105 | } 106 | } 107 | 108 | return nil 109 | } 110 | 111 | func (il *IL) loadTranslations(dirPath string) error { 112 | dir, err := os.Open(dirPath) 113 | if err != nil { 114 | return err 115 | } 116 | defer dir.Close() 117 | 118 | names, err := dir.Readdirnames(-1) 119 | if err != nil { 120 | return err 121 | } 122 | 123 | for _, name := range names { 124 | fullPath := path.Join(dirPath, name) 125 | 126 | fi, err := os.Stat(fullPath) 127 | if err != nil { 128 | return err 129 | } 130 | 131 | if fi.IsDir() { 132 | if err := il.loadTranslations(fullPath); err != nil { 133 | return err 134 | } 135 | } else if locale := il.matchingLocaleFromFileName(name); locale != "" { 136 | file, err := os.Open(fullPath) 137 | if err != nil { 138 | return err 139 | } 140 | defer file.Close() 141 | 142 | if err := il.loadTranslation(file, locale); err != nil { 143 | return err 144 | } 145 | } 146 | } 147 | 148 | return nil 149 | } 150 | 151 | func (il *IL) matchingLocaleFromFileName(name string) string { 152 | for _, locale := range il.locales { 153 | if strings.HasSuffix(name, fmt.Sprintf("-%s.tr", locale)) { 154 | return locale 155 | } 156 | } 157 | 158 | return "" 159 | } 160 | 161 | func sourceKey(source string, context []string) string { 162 | if len(context) == 0 { 163 | return source 164 | } 165 | 166 | return fmt.Sprintf("__%s__%s__", source, strings.Join(context, "__")) 167 | } 168 | 169 | func localesChainForLocale(locale string) []string { 170 | parts := strings.Split(locale, "_") 171 | if len(parts) > 2 { 172 | return nil 173 | } 174 | 175 | if len(parts[0]) != 2 { 176 | return nil 177 | } 178 | 179 | for _, r := range parts[0] { 180 | if r < rune('a') || r > rune('z') { 181 | return nil 182 | } 183 | } 184 | 185 | if len(parts) == 1 { 186 | return []string{parts[0]} 187 | } 188 | 189 | if len(parts[1]) < 2 || len(parts[1]) > 3 { 190 | return nil 191 | } 192 | 193 | for _, r := range parts[1] { 194 | if r < rune('A') || r > rune('Z') { 195 | return nil 196 | } 197 | } 198 | 199 | return []string{locale, parts[0]} 200 | } 201 | -------------------------------------------------------------------------------- /locales/en.i10n: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/astaxie/go-i18n/f979fd2aa531d5b6a77cc9635685121c66b123c0/locales/en.i10n -------------------------------------------------------------------------------- /locales/zh-CN.i10n: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/astaxie/go-i18n/f979fd2aa531d5b6a77cc9635685121c66b123c0/locales/zh-CN.i10n -------------------------------------------------------------------------------- /locales/zh-TW.i10n: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/astaxie/go-i18n/f979fd2aa531d5b6a77cc9635685121c66b123c0/locales/zh-TW.i10n --------------------------------------------------------------------------------