github.com/linchen2chris/hugo@v0.0.0-20230307053224-cec209389705/langs/i18n/translationProvider.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  	"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  }