translationProvider.go (3823B)
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 "encoding/json" 18 "fmt" 19 "strings" 20 21 "github.com/gohugoio/hugo/common/paths" 22 23 "github.com/gohugoio/hugo/common/herrors" 24 "golang.org/x/text/language" 25 yaml "gopkg.in/yaml.v2" 26 27 "github.com/gohugoio/go-i18n/v2/i18n" 28 "github.com/gohugoio/hugo/helpers" 29 toml "github.com/pelletier/go-toml/v2" 30 31 "github.com/gohugoio/hugo/deps" 32 "github.com/gohugoio/hugo/hugofs" 33 "github.com/gohugoio/hugo/source" 34 ) 35 36 // TranslationProvider provides translation handling, i.e. loading 37 // of bundles etc. 38 type TranslationProvider struct { 39 t Translator 40 } 41 42 // NewTranslationProvider creates a new translation provider. 43 func NewTranslationProvider() *TranslationProvider { 44 return &TranslationProvider{} 45 } 46 47 // Update updates the i18n func in the provided Deps. 48 func (tp *TranslationProvider) Update(d *deps.Deps) error { 49 spec := source.NewSourceSpec(d.PathSpec, nil, nil) 50 51 bundle := i18n.NewBundle(language.English) 52 bundle.RegisterUnmarshalFunc("toml", toml.Unmarshal) 53 bundle.RegisterUnmarshalFunc("yaml", yaml.Unmarshal) 54 bundle.RegisterUnmarshalFunc("yml", yaml.Unmarshal) 55 bundle.RegisterUnmarshalFunc("json", json.Unmarshal) 56 57 // The source dirs are ordered so the most important comes first. Since this is a 58 // last key win situation, we have to reverse the iteration order. 59 dirs := d.BaseFs.I18n.Dirs 60 for i := len(dirs) - 1; i >= 0; i-- { 61 dir := dirs[i] 62 src := spec.NewFilesystemFromFileMetaInfo(dir) 63 files, err := src.Files() 64 if err != nil { 65 return err 66 } 67 for _, file := range files { 68 if err := addTranslationFile(bundle, file); err != nil { 69 return err 70 } 71 } 72 } 73 74 tp.t = NewTranslator(bundle, d.Cfg, d.Log) 75 76 d.Translate = tp.t.Func(d.Language.Lang) 77 78 return nil 79 } 80 81 const artificialLangTagPrefix = "art-x-" 82 83 func addTranslationFile(bundle *i18n.Bundle, r source.File) error { 84 f, err := r.FileInfo().Meta().Open() 85 if err != nil { 86 return fmt.Errorf("failed to open translations file %q:: %w", r.LogicalName(), err) 87 } 88 89 b := helpers.ReaderToBytes(f) 90 f.Close() 91 92 name := r.LogicalName() 93 lang := paths.Filename(name) 94 tag := language.Make(lang) 95 if tag == language.Und { 96 try := artificialLangTagPrefix + lang 97 _, err = language.Parse(try) 98 if err != nil { 99 return fmt.Errorf("%q: %s", try, err) 100 } 101 name = artificialLangTagPrefix + name 102 } 103 104 _, err = bundle.ParseMessageFileBytes(b, name) 105 if err != nil { 106 if strings.Contains(err.Error(), "no plural rule") { 107 // https://github.com/gohugoio/hugo/issues/7798 108 name = artificialLangTagPrefix + name 109 _, err = bundle.ParseMessageFileBytes(b, name) 110 if err == nil { 111 return nil 112 } 113 } 114 return errWithFileContext(fmt.Errorf("failed to load translations: %w", err), r) 115 } 116 117 return nil 118 } 119 120 // Clone sets the language func for the new language. 121 func (tp *TranslationProvider) Clone(d *deps.Deps) error { 122 d.Translate = tp.t.Func(d.Language.Lang) 123 124 return nil 125 } 126 127 func errWithFileContext(inerr error, r source.File) error { 128 fim, ok := r.FileInfo().(hugofs.FileMetaInfo) 129 if !ok { 130 return inerr 131 } 132 133 meta := fim.Meta() 134 realFilename := meta.Filename 135 f, err := meta.Open() 136 if err != nil { 137 return inerr 138 } 139 defer f.Close() 140 141 return herrors.NewFileErrorFromName(inerr, realFilename).UpdateContent(f, nil) 142 143 }