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 }