URI: 
       i18n_test.go - hugo - [fork] hugo port for 9front
  HTML git clone https://git.drkhsh.at/hugo.git
   DIR Log
   DIR Files
   DIR Refs
   DIR Submodules
   DIR README
   DIR LICENSE
       ---
       i18n_test.go (13748B)
       ---
            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         "path/filepath"
           20         "testing"
           21 
           22         "github.com/bep/logg"
           23         "github.com/gohugoio/hugo/common/types"
           24         "github.com/gohugoio/hugo/config/testconfig"
           25 
           26         "github.com/gohugoio/hugo/resources/page"
           27         "github.com/spf13/afero"
           28 
           29         "github.com/gohugoio/hugo/deps"
           30 
           31         qt "github.com/frankban/quicktest"
           32         "github.com/gohugoio/hugo/config"
           33 )
           34 
           35 type i18nTest struct {
           36         name                             string
           37         data                             map[string][]byte
           38         args                             any
           39         lang, id, expected, expectedFlag string
           40 }
           41 
           42 var i18nTests = []i18nTest{
           43         // All translations present
           44         {
           45                 name: "all-present",
           46                 data: map[string][]byte{
           47                         "en.toml": []byte("[hello]\nother = \"Hello, World!\""),
           48                         "es.toml": []byte("[hello]\nother = \"¡Hola, Mundo!\""),
           49                 },
           50                 args:         nil,
           51                 lang:         "es",
           52                 id:           "hello",
           53                 expected:     "¡Hola, Mundo!",
           54                 expectedFlag: "¡Hola, Mundo!",
           55         },
           56         // Translation missing in current language but present in default
           57         {
           58                 name: "present-in-default",
           59                 data: map[string][]byte{
           60                         "en.toml": []byte("[hello]\nother = \"Hello, World!\""),
           61                         "es.toml": []byte("[goodbye]\nother = \"¡Adiós, Mundo!\""),
           62                 },
           63                 args:         nil,
           64                 lang:         "es",
           65                 id:           "hello",
           66                 expected:     "Hello, World!",
           67                 expectedFlag: "[i18n] hello",
           68         },
           69         // Translation missing in default language but present in current
           70         {
           71                 name: "present-in-current",
           72                 data: map[string][]byte{
           73                         "en.toml": []byte("[goodbye]\nother = \"Goodbye, World!\""),
           74                         "es.toml": []byte("[hello]\nother = \"¡Hola, Mundo!\""),
           75                 },
           76                 args:         nil,
           77                 lang:         "es",
           78                 id:           "hello",
           79                 expected:     "¡Hola, Mundo!",
           80                 expectedFlag: "¡Hola, Mundo!",
           81         },
           82         // Translation missing in both default and current language
           83         {
           84                 name: "missing",
           85                 data: map[string][]byte{
           86                         "en.toml": []byte("[goodbye]\nother = \"Goodbye, World!\""),
           87                         "es.toml": []byte("[goodbye]\nother = \"¡Adiós, Mundo!\""),
           88                 },
           89                 args:         nil,
           90                 lang:         "es",
           91                 id:           "hello",
           92                 expected:     "",
           93                 expectedFlag: "[i18n] hello",
           94         },
           95         // Default translation file missing or empty
           96         {
           97                 name: "file-missing",
           98                 data: map[string][]byte{
           99                         "en.toml": []byte(""),
          100                 },
          101                 args:         nil,
          102                 lang:         "es",
          103                 id:           "hello",
          104                 expected:     "",
          105                 expectedFlag: "[i18n] hello",
          106         },
          107         // Context provided
          108         {
          109                 name: "context-provided",
          110                 data: map[string][]byte{
          111                         "en.toml": []byte("[wordCount]\nother = \"Hello, {{.WordCount}} people!\""),
          112                         "es.toml": []byte("[wordCount]\nother = \"¡Hola, {{.WordCount}} gente!\""),
          113                 },
          114                 args: struct {
          115                         WordCount int
          116                 }{
          117                         50,
          118                 },
          119                 lang:         "es",
          120                 id:           "wordCount",
          121                 expected:     "¡Hola, 50 gente!",
          122                 expectedFlag: "¡Hola, 50 gente!",
          123         },
          124         // https://github.com/gohugoio/hugo/issues/7787
          125         {
          126                 name: "readingTime-one",
          127                 data: map[string][]byte{
          128                         "en.toml": []byte(`[readingTime]
          129 one = "One minute to read"
          130 other = "{{ .Count }} minutes to read"
          131 `),
          132                 },
          133                 args:         1,
          134                 lang:         "en",
          135                 id:           "readingTime",
          136                 expected:     "One minute to read",
          137                 expectedFlag: "One minute to read",
          138         },
          139         {
          140                 name: "readingTime-many-dot",
          141                 data: map[string][]byte{
          142                         "en.toml": []byte(`[readingTime]
          143 one = "One minute to read"
          144 other = "{{ . }} minutes to read"
          145 `),
          146                 },
          147                 args:         21,
          148                 lang:         "en",
          149                 id:           "readingTime",
          150                 expected:     "21 minutes to read",
          151                 expectedFlag: "21 minutes to read",
          152         },
          153         {
          154                 name: "readingTime-many",
          155                 data: map[string][]byte{
          156                         "en.toml": []byte(`[readingTime]
          157 one = "One minute to read"
          158 other = "{{ .Count }} minutes to read"
          159 `),
          160                 },
          161                 args:         21,
          162                 lang:         "en",
          163                 id:           "readingTime",
          164                 expected:     "21 minutes to read",
          165                 expectedFlag: "21 minutes to read",
          166         },
          167         // Issue #8454
          168         {
          169                 name: "readingTime-map-one",
          170                 data: map[string][]byte{
          171                         "en.toml": []byte(`[readingTime]
          172 one = "One minute to read"
          173 other = "{{ .Count }} minutes to read"
          174 `),
          175                 },
          176                 args:         map[string]any{"Count": 1},
          177                 lang:         "en",
          178                 id:           "readingTime",
          179                 expected:     "One minute to read",
          180                 expectedFlag: "One minute to read",
          181         },
          182         {
          183                 name: "readingTime-string-one",
          184                 data: map[string][]byte{
          185                         "en.toml": []byte(`[readingTime]
          186 one = "One minute to read"
          187 other = "{{ . }} minutes to read"
          188 `),
          189                 },
          190                 args:         "1",
          191                 lang:         "en",
          192                 id:           "readingTime",
          193                 expected:     "One minute to read",
          194                 expectedFlag: "One minute to read",
          195         },
          196         {
          197                 name: "readingTime-map-many",
          198                 data: map[string][]byte{
          199                         "en.toml": []byte(`[readingTime]
          200 one = "One minute to read"
          201 other = "{{ .Count }} minutes to read"
          202 `),
          203                 },
          204                 args:         map[string]any{"Count": 21},
          205                 lang:         "en",
          206                 id:           "readingTime",
          207                 expected:     "21 minutes to read",
          208                 expectedFlag: "21 minutes to read",
          209         },
          210         {
          211                 name: "argument-float",
          212                 data: map[string][]byte{
          213                         "en.toml": []byte(`[float]
          214 other = "Number is {{ . }}"
          215 `),
          216                 },
          217                 args:         22.5,
          218                 lang:         "en",
          219                 id:           "float",
          220                 expected:     "Number is 22.5",
          221                 expectedFlag: "Number is 22.5",
          222         },
          223         // Same id and translation in current language
          224         // https://github.com/gohugoio/hugo/issues/2607
          225         {
          226                 name: "same-id-and-translation",
          227                 data: map[string][]byte{
          228                         "es.toml": []byte("[hello]\nother = \"hello\""),
          229                         "en.toml": []byte("[hello]\nother = \"hi\""),
          230                 },
          231                 args:         nil,
          232                 lang:         "es",
          233                 id:           "hello",
          234                 expected:     "hello",
          235                 expectedFlag: "hello",
          236         },
          237         // Translation missing in current language, but same id and translation in default
          238         {
          239                 name: "same-id-and-translation-default",
          240                 data: map[string][]byte{
          241                         "es.toml": []byte("[bye]\nother = \"bye\""),
          242                         "en.toml": []byte("[hello]\nother = \"hello\""),
          243                 },
          244                 args:         nil,
          245                 lang:         "es",
          246                 id:           "hello",
          247                 expected:     "hello",
          248                 expectedFlag: "[i18n] hello",
          249         },
          250         // Unknown language code should get its plural spec from en
          251         {
          252                 name: "unknown-language-code",
          253                 data: map[string][]byte{
          254                         "en.toml": []byte(`[readingTime]
          255 one ="one minute read"
          256 other = "{{.Count}} minutes read"`),
          257                         "klingon.toml": []byte(`[readingTime]
          258 one =  "eitt minutt med lesing"
          259 other = "{{ .Count }} minuttar lesing"`),
          260                 },
          261                 args:         3,
          262                 lang:         "klingon",
          263                 id:           "readingTime",
          264                 expected:     "3 minuttar lesing",
          265                 expectedFlag: "3 minuttar lesing",
          266         },
          267         // Issue #7838
          268         {
          269                 name: "unknown-language-codes",
          270                 data: map[string][]byte{
          271                         "en.toml": []byte(`[readingTime]
          272 one ="en one"
          273 other = "en count {{.Count}}"`),
          274                         "a1.toml": []byte(`[readingTime]
          275 one =  "a1 one"
          276 other = "a1 count {{ .Count }}"`),
          277                         "a2.toml": []byte(`[readingTime]
          278 one =  "a2 one"
          279 other = "a2 count {{ .Count }}"`),
          280                 },
          281                 args:         3,
          282                 lang:         "a2",
          283                 id:           "readingTime",
          284                 expected:     "a2 count 3",
          285                 expectedFlag: "a2 count 3",
          286         },
          287         // https://github.com/gohugoio/hugo/issues/7798
          288         {
          289                 name: "known-language-missing-plural",
          290                 data: map[string][]byte{
          291                         "oc.toml": []byte(`[oc]
          292 one =  "abc"`),
          293                 },
          294                 args:         1,
          295                 lang:         "oc",
          296                 id:           "oc",
          297                 expected:     "abc",
          298                 expectedFlag: "abc",
          299         },
          300         // https://github.com/gohugoio/hugo/issues/7794
          301         {
          302                 name: "dotted-bare-key",
          303                 data: map[string][]byte{
          304                         "en.toml": []byte(`"shop_nextPage.one" = "Show Me The Money"
          305 `),
          306                 },
          307                 args:         nil,
          308                 lang:         "en",
          309                 id:           "shop_nextPage.one",
          310                 expected:     "Show Me The Money",
          311                 expectedFlag: "Show Me The Money",
          312         },
          313         // https: //github.com/gohugoio/hugo/issues/7804
          314         {
          315                 name: "lang-with-hyphen",
          316                 data: map[string][]byte{
          317                         "pt-br.toml": []byte(`foo.one =  "abc"`),
          318                 },
          319                 args:         1,
          320                 lang:         "pt-br",
          321                 id:           "foo",
          322                 expected:     "abc",
          323                 expectedFlag: "abc",
          324         },
          325 }
          326 
          327 func TestPlural(t *testing.T) {
          328         c := qt.New(t)
          329 
          330         for _, test := range []struct {
          331                 name     string
          332                 lang     string
          333                 id       string
          334                 templ    string
          335                 variants []types.KeyValue
          336         }{
          337                 {
          338                         name: "English",
          339                         lang: "en",
          340                         id:   "hour",
          341                         templ: `
          342 [hour]
          343 one = "{{ . }} hour"
          344 other = "{{ . }} hours"`,
          345                         variants: []types.KeyValue{
          346                                 {Key: 1, Value: "1 hour"},
          347                                 {Key: "1", Value: "1 hour"},
          348                                 {Key: 1.5, Value: "1.5 hours"},
          349                                 {Key: "1.5", Value: "1.5 hours"},
          350                                 {Key: 2, Value: "2 hours"},
          351                                 {Key: "2", Value: "2 hours"},
          352                         },
          353                 },
          354                 {
          355                         name: "Other only",
          356                         lang: "en",
          357                         id:   "hour",
          358                         templ: `
          359 [hour]
          360 other = "{{ with . }}{{ . }}{{ end }} hours"`,
          361                         variants: []types.KeyValue{
          362                                 {Key: 1, Value: "1 hours"},
          363                                 {Key: "1", Value: "1 hours"},
          364                                 {Key: 2, Value: "2 hours"},
          365                                 {Key: nil, Value: " hours"},
          366                         },
          367                 },
          368                 {
          369                         name: "Polish",
          370                         lang: "pl",
          371                         id:   "day",
          372                         templ: `
          373 [day]
          374 one = "{{ . }} miesiąc"
          375 few = "{{ . }} miesiące"
          376 many = "{{ . }} miesięcy"
          377 other = "{{ . }} miesiąca"
          378 `,
          379                         variants: []types.KeyValue{
          380                                 {Key: 1, Value: "1 miesiąc"},
          381                                 {Key: 2, Value: "2 miesiące"},
          382                                 {Key: 100, Value: "100 miesięcy"},
          383                                 {Key: "100.0", Value: "100.0 miesiąca"},
          384                                 {Key: 100.0, Value: "100 miesiąca"},
          385                         },
          386                 },
          387         } {
          388                 c.Run(test.name, func(c *qt.C) {
          389                         cfg := config.New()
          390                         cfg.Set("enableMissingTranslationPlaceholders", true)
          391                         cfg.Set("publishDir", "public")
          392                         afs := afero.NewMemMapFs()
          393 
          394                         err := afero.WriteFile(afs, filepath.Join("i18n", test.lang+".toml"), []byte(test.templ), 0o755)
          395                         c.Assert(err, qt.IsNil)
          396 
          397                         d, tp := prepareDeps(afs, cfg)
          398 
          399                         f := tp.t.Func(test.lang)
          400                         ctx := context.Background()
          401 
          402                         for _, variant := range test.variants {
          403                                 c.Assert(f(ctx, test.id, variant.Key), qt.Equals, variant.Value, qt.Commentf("input: %v", variant.Key))
          404                                 c.Assert(d.Log.LoggCount(logg.LevelWarn), qt.Equals, 0)
          405                         }
          406                 })
          407         }
          408 }
          409 
          410 func doTestI18nTranslate(t testing.TB, test i18nTest, cfg config.Provider) string {
          411         tp := prepareTranslationProvider(t, test, cfg)
          412         f := tp.t.Func(test.lang)
          413         return f(context.Background(), test.id, test.args)
          414 }
          415 
          416 type countField struct {
          417         Count any
          418 }
          419 
          420 type noCountField struct {
          421         Counts int
          422 }
          423 
          424 type countMethod struct{}
          425 
          426 func (c countMethod) Count() any {
          427         return 32.5
          428 }
          429 
          430 func TestGetPluralCount(t *testing.T) {
          431         c := qt.New(t)
          432 
          433         c.Assert(getPluralCount(map[string]any{"Count": 32}), qt.Equals, 32)
          434         c.Assert(getPluralCount(map[string]any{"Count": 1}), qt.Equals, 1)
          435         c.Assert(getPluralCount(map[string]any{"Count": 1.5}), qt.Equals, "1.5")
          436         c.Assert(getPluralCount(map[string]any{"Count": "32"}), qt.Equals, "32")
          437         c.Assert(getPluralCount(map[string]any{"Count": "32.5"}), qt.Equals, "32.5")
          438         c.Assert(getPluralCount(map[string]any{"count": 32}), qt.Equals, 32)
          439         c.Assert(getPluralCount(map[string]any{"Count": "32"}), qt.Equals, "32")
          440         c.Assert(getPluralCount(map[string]any{"Counts": 32}), qt.Equals, nil)
          441         c.Assert(getPluralCount("foo"), qt.Equals, nil)
          442         c.Assert(getPluralCount(countField{Count: 22}), qt.Equals, 22)
          443         c.Assert(getPluralCount(countField{Count: 1.5}), qt.Equals, "1.5")
          444         c.Assert(getPluralCount(&countField{Count: 22}), qt.Equals, 22)
          445         c.Assert(getPluralCount(noCountField{Counts: 23}), qt.Equals, nil)
          446         c.Assert(getPluralCount(countMethod{}), qt.Equals, "32.5")
          447         c.Assert(getPluralCount(&countMethod{}), qt.Equals, "32.5")
          448 
          449         c.Assert(getPluralCount(1234), qt.Equals, 1234)
          450         c.Assert(getPluralCount(1234.4), qt.Equals, "1234.4")
          451         c.Assert(getPluralCount(1234.0), qt.Equals, "1234.0")
          452         c.Assert(getPluralCount("1234"), qt.Equals, "1234")
          453         c.Assert(getPluralCount("0.5"), qt.Equals, "0.5")
          454         c.Assert(getPluralCount(nil), qt.Equals, nil)
          455 }
          456 
          457 func prepareTranslationProvider(t testing.TB, test i18nTest, cfg config.Provider) *TranslationProvider {
          458         c := qt.New(t)
          459         afs := afero.NewMemMapFs()
          460 
          461         for file, content := range test.data {
          462                 err := afero.WriteFile(afs, filepath.Join("i18n", file), []byte(content), 0o755)
          463                 c.Assert(err, qt.IsNil)
          464         }
          465 
          466         _, tp := prepareDeps(afs, cfg)
          467         return tp
          468 }
          469 
          470 func prepareDeps(afs afero.Fs, cfg config.Provider) (*deps.Deps, *TranslationProvider) {
          471         d := testconfig.GetTestDeps(afs, cfg)
          472         translationProvider := NewTranslationProvider()
          473         d.TranslationProvider = translationProvider
          474         d.Site = page.NewDummyHugoSite(d.Conf)
          475         if err := d.Compile(nil); err != nil {
          476                 panic(err)
          477         }
          478         return d, translationProvider
          479 }
          480 
          481 func TestI18nTranslate(t *testing.T) {
          482         c := qt.New(t)
          483         var actual, expected string
          484         v := config.New()
          485 
          486         // Test without and with placeholders
          487         for _, enablePlaceholders := range []bool{false, true} {
          488                 v.Set("enableMissingTranslationPlaceholders", enablePlaceholders)
          489 
          490                 for _, test := range i18nTests {
          491                         c.Run(fmt.Sprintf("%s-%t", test.name, enablePlaceholders), func(c *qt.C) {
          492                                 if enablePlaceholders {
          493                                         expected = test.expectedFlag
          494                                 } else {
          495                                         expected = test.expected
          496                                 }
          497                                 actual = doTestI18nTranslate(c, test, v)
          498                                 c.Assert(actual, qt.Equals, expected)
          499                         })
          500                 }
          501         }
          502 }
          503 
          504 func BenchmarkI18nTranslate(b *testing.B) {
          505         v := config.New()
          506         for _, test := range i18nTests {
          507                 b.Run(test.name, func(b *testing.B) {
          508                         tp := prepareTranslationProvider(b, test, v)
          509                         b.ResetTimer()
          510                         for i := 0; i < b.N; i++ {
          511                                 f := tp.t.Func(test.lang)
          512                                 actual := f(context.Background(), test.id, test.args)
          513                                 if actual != test.expected {
          514                                         b.Fatalf("expected %v got %v", test.expected, actual)
          515                                 }
          516                         }
          517                 })
          518         }
          519 }