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 }