Skip to content
This repository has been archived by the owner on Feb 24, 2024. It is now read-only.

fix template based i18n support issues #2259

Merged
merged 2 commits into from
May 15, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
81 changes: 58 additions & 23 deletions render/template.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"sync"

"github.com/sirupsen/logrus"
"golang.org/x/text/language"
)

type templateRenderer struct {
Expand Down Expand Up @@ -79,21 +80,28 @@ func (s *templateRenderer) updateAliases() error {
return nil
}

base := filepath.Base(path)
dir := filepath.Dir(path)
// index.plush.ko-kr.html as index.ko-kr.html
shortcut := strings.Replace(path, ".plush.", ".", 1)
s.aliases.Store(shortcut, path)

// register short version (lang only) of shortcut
words := strings.Split(filepath.Base(shortcut), ".")
if len(words) > 2 {
for _, w := range words[1 : len(words)-1] {
if tag, err := language.Parse(w); err == nil {
base, confidence := tag.Base()
if confidence == language.Exact || confidence == language.High {
// index.plush.ko-kr.html as index.ko.html
shortcut := strings.Replace(shortcut, w, base.String(), 1)
s.aliases.Store(shortcut, path)

// index.plush.ko-kr.html as index.plush.ko.html
shortcut = strings.Replace(path, w, base.String(), 1)
s.aliases.Store(shortcut, path)
}
}

var exts []string
sep := strings.Split(base, ".")
if len(sep) >= 1 {
base = sep[0]
}
if len(sep) > 1 {
exts = sep[1:]
}

for _, ext := range exts {
pn := filepath.Join(dir, base+"."+ext)
s.aliases.Store(pn, path)
}
}

return nil
Expand Down Expand Up @@ -134,15 +142,7 @@ func (s *templateRenderer) exec(name string, data Data) (template.HTML, error) {

name = fixExtension(name, ct)

// Try to use localized version
templateName := s.localizedName(name, data)

// Set current_template to context
if _, ok := data["current_template"]; !ok {
data["current_template"] = templateName
}

source, err := s.resolve(templateName)
source, err := s.localizedResolve(name, data)
if err != nil {
return "", err
}
Expand Down Expand Up @@ -176,6 +176,7 @@ func (s *templateRenderer) exec(name string, data Data) (template.HTML, error) {
return template.HTML(body), nil
}

// next step, deprecate if this is no longer required.
func (s *templateRenderer) localizedName(name string, data Data) string {
templateName := name

Expand Down Expand Up @@ -207,6 +208,40 @@ func (s *templateRenderer) localizedName(name string, data Data) string {
return templateName
}

func (s *templateRenderer) localizedResolve(name string, data Data) ([]byte, error) {
languages, ok := data["languages"].([]string)
if !ok || len(languages) == 0 {
return s.resolve(name)
}

defaultLang := languages[len(languages)-1] // default language
ext := filepath.Ext(name)
rawName := strings.TrimSuffix(name, ext)

for _, lang := range languages {
if lang == defaultLang {
break
}

fullLower := strings.ToLower(lang) // forms of ko-kr or ko
short := strings.Split(fullLower, "-")[0] // form of ko

fullLocale := rawName + "." + fullLower + ext
if source, err := s.resolve(fullLocale); err == nil {
return source, nil
}

if fullLower != short {
langOnly := rawName + "." + short + ext
if source, err := s.resolve(langOnly); err == nil {
return source, nil
}
}
}

return s.resolve(name)
}

func (s *templateRenderer) exts(name string) []string {
exts := []string{}
for {
Expand Down
223 changes: 223 additions & 0 deletions render/template_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -105,3 +105,226 @@ func Test_AssetPathNoManifestCorrupt(t *testing.T) {
r.NotEqual(expected, strings.TrimSpace(bb.String()))
}
}

/* test if i18n files (both with plush mid-extension and latecy) proceeded correctly.
*/
func Test_Template_resolve_DefaultLang_Plush(t *testing.T) {
r := require.New(t)

rootFS := memfs.New()
r.NoError(rootFS.WriteFile("index.plush.html", []byte("default <%= name %>"), 0644))
r.NoError(rootFS.WriteFile("index.plush.ko-kr.html", []byte("korean <%= name %>"), 0644))

e := NewEngine()
e.TemplatesFS = rootFS

re := e.Template("foo/bar", "index.plush.html")
r.Equal("foo/bar", re.ContentType())

bb := &bytes.Buffer{}
r.NoError(re.Render(bb, Data{"name": "Paul", "languages": []string{"es", "en"}}))
r.Equal("default Paul", strings.TrimSpace(bb.String()))
}

func Test_Template_resolve_UserLang_Plush(t *testing.T) {
r := require.New(t)

rootFS := memfs.New()
r.NoError(rootFS.WriteFile("index.plush.html", []byte("default <%= name %>"), 0644))
r.NoError(rootFS.WriteFile("index.plush.ko-kr.html", []byte("korean <%= name %>"), 0644))

e := NewEngine()
e.TemplatesFS = rootFS

re := e.Template("foo/bar", "index.plush.html")
r.Equal("foo/bar", re.ContentType())

bb := &bytes.Buffer{}
r.NoError(re.Render(bb, Data{"name": "Paul", "languages": []string{"ko-KR", "en"}}))
r.Equal("korean Paul", strings.TrimSpace(bb.String()))
}

func Test_Template_resolve_DefaultLang_Legacy(t *testing.T) {
r := require.New(t)

rootFS := memfs.New()
r.NoError(rootFS.WriteFile("index.html", []byte("default <%= name %>"), 0644))
r.NoError(rootFS.WriteFile("index.ko-kr.html", []byte("korean <%= name %>"), 0644))

e := NewEngine()
e.TemplatesFS = rootFS

re := e.Template("foo/bar", "index.html")
r.Equal("foo/bar", re.ContentType())

bb := &bytes.Buffer{}
r.NoError(re.Render(bb, Data{"name": "Paul", "languages": []string{"es", "en"}}))
r.Equal("default Paul", strings.TrimSpace(bb.String()))
}

func Test_Template_resolve_UserLang_Legacy(t *testing.T) {
r := require.New(t)

rootFS := memfs.New()
r.NoError(rootFS.WriteFile("index.html", []byte("default <%= name %>"), 0644))
r.NoError(rootFS.WriteFile("index.ko-kr.html", []byte("korean <%= name %>"), 0644))

e := NewEngine()
e.TemplatesFS = rootFS

re := e.Template("foo/bar", "index.html")
r.Equal("foo/bar", re.ContentType())

bb := &bytes.Buffer{}
r.NoError(re.Render(bb, Data{"name": "Paul", "languages": []string{"ko-KR", "en"}}))
r.Equal("korean Paul", strings.TrimSpace(bb.String()))
}

func Test_Template_resolve_DefaultLang_Mixed(t *testing.T) {
r := require.New(t)

rootFS := memfs.New()
r.NoError(rootFS.WriteFile("index.plush.html", []byte("default <%= name %>"), 0644))
r.NoError(rootFS.WriteFile("index.plush.ko-kr.html", []byte("korean <%= name %>"), 0644))

e := NewEngine()
e.TemplatesFS = rootFS

// `buffalo fix` renames templates but does not fix actions
// in this case, aliases will be used for template matching
re := e.Template("foo/bar", "index.html")
r.Equal("foo/bar", re.ContentType())

bb := &bytes.Buffer{}
r.NoError(re.Render(bb, Data{"name": "Paul", "languages": []string{"es", "en"}}))
r.Equal("default Paul", strings.TrimSpace(bb.String()))
}

func Test_Template_resolve_UserLang_Mixed(t *testing.T) {
r := require.New(t)

rootFS := memfs.New()
r.NoError(rootFS.WriteFile("index.plush.html", []byte("default <%= name %>"), 0644))
r.NoError(rootFS.WriteFile("index.plush.ko-kr.html", []byte("korean <%= name %>"), 0644))

e := NewEngine()
e.TemplatesFS = rootFS

// `buffalo fix` renames templates but does not fix actions
// in this case, aliases will be used for template matching
re := e.Template("foo/bar", "index.html")
r.Equal("foo/bar", re.ContentType())

bb := &bytes.Buffer{}
r.NoError(re.Render(bb, Data{"name": "Paul", "languages": []string{"ko-KR", "en"}}))
r.Equal("korean Paul", strings.TrimSpace(bb.String()))
}

// support short language-only version of template e.g. index.plush.ko.html
func Test_Template_resolve_FullLocale_ShortFile(t *testing.T) {
r := require.New(t)

rootFS := memfs.New()
r.NoError(rootFS.WriteFile("index.plush.html", []byte("default <%= name %>"), 0644))
r.NoError(rootFS.WriteFile("index.plush.ko.html", []byte("korean <%= name %>"), 0644))

e := NewEngine()
e.TemplatesFS = rootFS

re := e.Template("foo/bar", "index.plush.html")
r.Equal("foo/bar", re.ContentType())

bb := &bytes.Buffer{}
r.NoError(re.Render(bb, Data{"name": "Paul", "languages": []string{"ko-KR", "en"}}))
r.Equal("korean Paul", strings.TrimSpace(bb.String()))
}

func Test_Template_resolve_LangOnly_FullFile(t *testing.T) {
r := require.New(t)

rootFS := memfs.New()
r.NoError(rootFS.WriteFile("index.plush.html", []byte("default <%= name %>"), 0644))
r.NoError(rootFS.WriteFile("index.plush.ko-kr.html", []byte("korean <%= name %>"), 0644))

e := NewEngine()
e.TemplatesFS = rootFS

re := e.Template("foo/bar", "index.plush.html")
r.Equal("foo/bar", re.ContentType())

bb := &bytes.Buffer{}
r.NoError(re.Render(bb, Data{"name": "Paul", "languages": []string{"ko", "en"}}))
r.Equal("korean Paul", strings.TrimSpace(bb.String()))
}

func Test_Template_resolve_FullLocale_ShortFile_Legacy(t *testing.T) {
r := require.New(t)

rootFS := memfs.New()
r.NoError(rootFS.WriteFile("index.html", []byte("default <%= name %>"), 0644))
r.NoError(rootFS.WriteFile("index.ko.html", []byte("korean <%= name %>"), 0644))

e := NewEngine()
e.TemplatesFS = rootFS

re := e.Template("foo/bar", "index.html")
r.Equal("foo/bar", re.ContentType())

bb := &bytes.Buffer{}
r.NoError(re.Render(bb, Data{"name": "Paul", "languages": []string{"ko-KR", "en"}}))
r.Equal("korean Paul", strings.TrimSpace(bb.String()))
}

func Test_Template_resolve_LangOnly_FullFile_Legacy(t *testing.T) {
r := require.New(t)

rootFS := memfs.New()
r.NoError(rootFS.WriteFile("index.html", []byte("default <%= name %>"), 0644))
r.NoError(rootFS.WriteFile("index.ko-kr.html", []byte("korean <%= name %>"), 0644))

e := NewEngine()
e.TemplatesFS = rootFS

re := e.Template("foo/bar", "index.html")
r.Equal("foo/bar", re.ContentType())

bb := &bytes.Buffer{}
r.NoError(re.Render(bb, Data{"name": "Paul", "languages": []string{"ko", "en"}}))
r.Equal("korean Paul", strings.TrimSpace(bb.String()))
}

func Test_Template_resolve_FullLocale_ShortFile_Mixed(t *testing.T) {
r := require.New(t)

rootFS := memfs.New()
r.NoError(rootFS.WriteFile("index.plush.html", []byte("default <%= name %>"), 0644))
r.NoError(rootFS.WriteFile("index.plush.ko.html", []byte("korean <%= name %>"), 0644))

e := NewEngine()
e.TemplatesFS = rootFS

re := e.Template("foo/bar", "index.html")
r.Equal("foo/bar", re.ContentType())

bb := &bytes.Buffer{}
r.NoError(re.Render(bb, Data{"name": "Paul", "languages": []string{"ko-KR", "en"}}))
r.Equal("korean Paul", strings.TrimSpace(bb.String()))
}

func Test_Template_resolve_LangOnly_FullFile_Mixed(t *testing.T) {
r := require.New(t)

rootFS := memfs.New()
r.NoError(rootFS.WriteFile("index.plush.html", []byte("default <%= name %>"), 0644))
r.NoError(rootFS.WriteFile("index.plush.ko-kr.html", []byte("korean <%= name %>"), 0644))

e := NewEngine()
e.TemplatesFS = rootFS

re := e.Template("foo/bar", "index.html")
r.Equal("foo/bar", re.ContentType())

bb := &bytes.Buffer{}
r.NoError(re.Render(bb, Data{"name": "Paul", "languages": []string{"ko", "en"}}))
r.Equal("korean Paul", strings.TrimSpace(bb.String()))
}