github.com/anakojm/hugo-katex@v0.0.0-20231023141351-42d6f5de9c0b/langs/i18n/i18n.go (about) 1 // Copyright 2017 The Hugo Authors. All rights reserved. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // http://www.apache.org/licenses/LICENSE-2.0 7 // 8 // Unless required by applicable law or agreed to in writing, software 9 // distributed under the License is distributed on an "AS IS" BASIS, 10 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 // See the License for the specific language governing permissions and 12 // limitations under the License. 13 14 package i18n 15 16 import ( 17 "context" 18 "fmt" 19 "reflect" 20 "strings" 21 22 "github.com/spf13/cast" 23 24 "github.com/gohugoio/hugo/common/hreflect" 25 "github.com/gohugoio/hugo/common/loggers" 26 "github.com/gohugoio/hugo/config" 27 "github.com/gohugoio/hugo/resources/page" 28 29 "github.com/gohugoio/go-i18n/v2/i18n" 30 ) 31 32 type translateFunc func(ctx context.Context, translationID string, templateData any) string 33 34 // Translator handles i18n translations. 35 type Translator struct { 36 translateFuncs map[string]translateFunc 37 cfg config.AllProvider 38 logger loggers.Logger 39 } 40 41 // NewTranslator creates a new Translator for the given language bundle and configuration. 42 func NewTranslator(b *i18n.Bundle, cfg config.AllProvider, logger loggers.Logger) Translator { 43 t := Translator{cfg: cfg, logger: logger, translateFuncs: make(map[string]translateFunc)} 44 t.initFuncs(b) 45 return t 46 } 47 48 // Func gets the translate func for the given language, or for the default 49 // configured language if not found. 50 func (t Translator) Func(lang string) translateFunc { 51 if f, ok := t.translateFuncs[lang]; ok { 52 return f 53 } 54 t.logger.Infof("Translation func for language %v not found, use default.", lang) 55 if f, ok := t.translateFuncs[t.cfg.DefaultContentLanguage()]; ok { 56 return f 57 } 58 59 t.logger.Infoln("i18n not initialized; if you need string translations, check that you have a bundle in /i18n that matches the site language or the default language.") 60 return func(ctx context.Context, translationID string, args any) string { 61 return "" 62 } 63 } 64 65 func (t Translator) initFuncs(bndl *i18n.Bundle) { 66 enableMissingTranslationPlaceholders := t.cfg.EnableMissingTranslationPlaceholders() 67 for _, lang := range bndl.LanguageTags() { 68 currentLang := lang 69 currentLangStr := currentLang.String() 70 // This may be pt-BR; make it case insensitive. 71 currentLangKey := strings.ToLower(strings.TrimPrefix(currentLangStr, artificialLangTagPrefix)) 72 localizer := i18n.NewLocalizer(bndl, currentLangStr) 73 t.translateFuncs[currentLangKey] = func(ctx context.Context, translationID string, templateData any) string { 74 pluralCount := getPluralCount(templateData) 75 76 if templateData != nil { 77 tp := reflect.TypeOf(templateData) 78 if hreflect.IsInt(tp.Kind()) { 79 // This was how go-i18n worked in v1, 80 // and we keep it like this to avoid breaking 81 // lots of sites in the wild. 82 templateData = intCount(cast.ToInt(templateData)) 83 } else { 84 if p, ok := templateData.(page.Page); ok { 85 // See issue 10782. 86 // The i18n has its own template handling and does not know about 87 // the context.Context. 88 // A common pattern is to pass Page to i18n, and use .ReadingTime etc. 89 // We need to improve this, but that requires some upstream changes. 90 // For now, just creata a wrepper. 91 templateData = page.PageWithContext{Page: p, Ctx: ctx} 92 } 93 } 94 } 95 96 translated, translatedLang, err := localizer.LocalizeWithTag(&i18n.LocalizeConfig{ 97 MessageID: translationID, 98 TemplateData: templateData, 99 PluralCount: pluralCount, 100 }) 101 102 sameLang := currentLang == translatedLang 103 104 if err == nil && sameLang { 105 return translated 106 } 107 108 if err != nil && sameLang && translated != "" { 109 // See #8492 110 // TODO(bep) this needs to be improved/fixed upstream, 111 // but currently we get an error even if the fallback to 112 // "other" succeeds. 113 if fmt.Sprintf("%T", err) == "i18n.pluralFormNotFoundError" { 114 return translated 115 } 116 } 117 118 if _, ok := err.(*i18n.MessageNotFoundErr); !ok { 119 t.logger.Warnf("Failed to get translated string for language %q and ID %q: %s", currentLangStr, translationID, err) 120 } 121 122 if t.cfg.PrintI18nWarnings() { 123 t.logger.Warnf("i18n|MISSING_TRANSLATION|%s|%s", currentLangStr, translationID) 124 } 125 126 if enableMissingTranslationPlaceholders { 127 return "[i18n] " + translationID 128 } 129 130 return translated 131 } 132 } 133 } 134 135 // intCount wraps the Count method. 136 type intCount int 137 138 func (c intCount) Count() int { 139 return int(c) 140 } 141 142 const countFieldName = "Count" 143 144 // getPluralCount gets the plural count as a string (floats) or an integer. 145 // If v is nil, nil is returned. 146 func getPluralCount(v any) any { 147 if v == nil { 148 // i18n called without any argument, make sure it does not 149 // get any plural count. 150 return nil 151 } 152 153 switch v := v.(type) { 154 case map[string]any: 155 for k, vv := range v { 156 if strings.EqualFold(k, countFieldName) { 157 return toPluralCountValue(vv) 158 } 159 } 160 default: 161 vv := reflect.Indirect(reflect.ValueOf(v)) 162 if vv.Kind() == reflect.Interface && !vv.IsNil() { 163 vv = vv.Elem() 164 } 165 tp := vv.Type() 166 167 if tp.Kind() == reflect.Struct { 168 f := vv.FieldByName(countFieldName) 169 if f.IsValid() { 170 return toPluralCountValue(f.Interface()) 171 } 172 m := hreflect.GetMethodByName(vv, countFieldName) 173 if m.IsValid() && m.Type().NumIn() == 0 && m.Type().NumOut() == 1 { 174 c := m.Call(nil) 175 return toPluralCountValue(c[0].Interface()) 176 } 177 } 178 } 179 180 return toPluralCountValue(v) 181 } 182 183 // go-i18n expects floats to be represented by string. 184 func toPluralCountValue(in any) any { 185 k := reflect.TypeOf(in).Kind() 186 switch { 187 case hreflect.IsFloat(k): 188 f := cast.ToString(in) 189 if !strings.Contains(f, ".") { 190 f += ".0" 191 } 192 return f 193 case k == reflect.String: 194 if _, err := cast.ToFloat64E(in); err == nil { 195 return in 196 } 197 // A non-numeric value. 198 return nil 199 default: 200 if i, err := cast.ToIntE(in); err == nil { 201 return i 202 } 203 return nil 204 } 205 }