diff --git a/.github/README.zh-Hans.md b/.github/README.zh-Hans.md
index a7f2c4bc..8d458948 100644
--- a/.github/README.zh-Hans.md
+++ b/.github/README.zh-Hans.md
@@ -1,4 +1,5 @@
-# go-i18n  [](https://goreportcard.com/report/github.com/nicksnyder/go-i18n) [](https://codecov.io/gh/nicksnyder/go-i18n) [](https://sourcegraph.com/github.com/nicksnyder/go-i18n?badge)
+# go-i18n
+ [](https://goreportcard.com/report/github.com/nicksnyder/go-i18n/v2) [](https://codecov.io/gh/nicksnyder/go-i18n) [](https://sourcegraph.com/github.com/nicksnyder/go-i18n?badge)
go-i18n 是一个帮助您将 Go 程序翻译成多种语言的 Go [包](#package-i18n)和[命令](#command-goi18n)。
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
new file mode 100644
index 00000000..768bcda1
--- /dev/null
+++ b/.github/dependabot.yml
@@ -0,0 +1,10 @@
+version: 2
+updates:
+ - package-ecosystem: "github-actions"
+ directory: "/"
+ schedule:
+ interval: "monthly"
+ - package-ecosystem: "gomod"
+ directory: "/"
+ schedule:
+ interval: "monthly"
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 2d3279e1..da6035e5 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -1,39 +1,44 @@
name: Build
on:
- - push
- - pull_request
+ push:
+ branches:
+ - main
+ pull_request:
+
jobs:
build:
name: Build
runs-on: ubuntu-latest
- if: (github.event_name == 'push' && github.ref == 'refs/heads/main') || github.event_name == 'pull_request'
steps:
- name: Install Go
- uses: actions/setup-go@v4
+ uses: actions/setup-go@v5
with:
go-version: stable
- name: Git checkout
- uses: actions/checkout@v3
+ uses: actions/checkout@v4
- name: Build
- uses: goreleaser/goreleaser-action@v2
+ uses: goreleaser/goreleaser-action@v6
with:
version: latest
args: release --clean --snapshot
- name: Test
run: go test -race -coverprofile=coverage.txt -covermode=atomic ./...
- name: Upload coverage
- uses: codecov/codecov-action@v3
+ uses: codecov/codecov-action@v4
+ with:
+ token: ${{ secrets.CODECOV_TOKEN }}
+ fail_ci_if_error: true
build_1_18:
name: Build with Go 1.18
runs-on: ubuntu-latest
if: (github.event_name == 'push' && github.ref == 'refs/heads/main') || github.event_name == 'pull_request'
steps:
- name: Install Go
- uses: actions/setup-go@v4
+ uses: actions/setup-go@v5
with:
go-version: '1.18'
- name: Git checkout
- uses: actions/checkout@v3
+ uses: actions/checkout@v4
- name: Build and test
run: |
go get ./...
diff --git a/.github/workflows/golangci-lint.yml b/.github/workflows/golangci-lint.yml
index 3c0f0a68..bb05c462 100644
--- a/.github/workflows/golangci-lint.yml
+++ b/.github/workflows/golangci-lint.yml
@@ -13,12 +13,11 @@ jobs:
name: lint
runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v3
- - uses: actions/setup-go@v4
+ - uses: actions/checkout@v4
+ - uses: actions/setup-go@v5
with:
- go-version: '1.21'
- cache: false
+ go-version: stable
- name: golangci-lint
- uses: golangci/golangci-lint-action@v3
+ uses: golangci/golangci-lint-action@v6
with:
- version: v1.55
+ version: v1.61
diff --git a/.github/workflows/goreleaser.yml b/.github/workflows/goreleaser.yml
index 71fc82fe..7853fdc4 100644
--- a/.github/workflows/goreleaser.yml
+++ b/.github/workflows/goreleaser.yml
@@ -11,17 +11,17 @@ jobs:
steps:
-
name: Checkout
- uses: actions/checkout@v2
+ uses: actions/checkout@v4
with:
fetch-depth: 0
-
name: Set up Go
- uses: actions/setup-go@v2
+ uses: actions/setup-go@v5
with:
- go-version: '^1.19.3'
+ go-version: stable
-
name: Release
- uses: goreleaser/goreleaser-action@v2
+ uses: goreleaser/goreleaser-action@v6
with:
version: latest
args: release --clean
diff --git a/.github/workflows/lsif-go.yml b/.github/workflows/lsif-go.yml
deleted file mode 100644
index d5d8e107..00000000
--- a/.github/workflows/lsif-go.yml
+++ /dev/null
@@ -1,15 +0,0 @@
-name: Sourcegraph code intelligence
-on:
- - push
-
-jobs:
- lsif-go:
- runs-on: ubuntu-latest
- container: sourcegraph/lsif-go:latest
- steps:
- - uses: actions/checkout@v1
- - name: Generate LSIF data
- run: lsif-go
- - name: Upload LSIF data to Sourcegraph.com
- run: src lsif upload -github-token=${{ secrets.GITHUB_TOKEN }} -ignore-upload-failure
-
diff --git a/.github/workflows/scip-go.yml b/.github/workflows/scip-go.yml
new file mode 100644
index 00000000..4c444d03
--- /dev/null
+++ b/.github/workflows/scip-go.yml
@@ -0,0 +1,23 @@
+name: Sourcegraph code intelligence
+on:
+ - push
+
+jobs:
+ scip-go:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+ - name: Install scip-go
+ run: |
+ curl -L https://github.com/sourcegraph/scip-go/releases/download/v0.1.21/scip-go_0.1.21_linux_amd64.tar.gz -o scip-go.tar.gz --no-progress-meter
+ tar -xf scip-go.tar.gz
+ chmod +x ./scip-go
+ - name: Install src
+ run: |
+ curl -L https://sourcegraph.com/.api/src-cli/src_linux_amd64 -o src --no-progress-meter
+ chmod +x ./src
+ - name: Generate SCIP data
+ run: ./scip-go
+ - name: Upload SCIP data to Sourcegraph.com
+ run: SRC_ACCESS_TOKEN=${{ secrets.SRC_ACCESS_TOKEN }} ./src code-intel upload -github-token=${{ secrets.GITHUB_TOKEN }} -no-progress
+
diff --git a/.goreleaser.yml b/.goreleaser.yml
index d9352adb..a2d3b837 100644
--- a/.goreleaser.yml
+++ b/.goreleaser.yml
@@ -1,3 +1,4 @@
+version: 2
builds:
- binary: goi18n
main: ./goi18n/
diff --git a/README.md b/README.md
index c6e09f95..0b826e31 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,5 @@
-# go-i18n  [](https://goreportcard.com/report/github.com/nicksnyder/go-i18n) [](https://codecov.io/gh/nicksnyder/go-i18n) [](https://sourcegraph.com/github.com/nicksnyder/go-i18n?badge)
+# go-i18n
+ [](https://goreportcard.com/report/github.com/nicksnyder/go-i18n/v2) [](https://codecov.io/gh/nicksnyder/go-i18n) [](https://sourcegraph.com/github.com/nicksnyder/go-i18n?badge)
go-i18n is a Go [package](#package-i18n) and a [command](#command-goi18n) that helps you translate Go programs into multiple languages.
@@ -7,14 +8,6 @@ go-i18n is a Go [package](#package-i18n) and a [command](#command-goi18n) that h
- Supports strings with named variables using [text/template](http://golang.org/pkg/text/template/) syntax.
- Supports message files of any format (e.g. JSON, TOML, YAML).
-
-
-
-[**English**](README.md) · [**简体中文**](.github/README.zh-Hans.md)
-
-
-
-
## Package i18n
[](https://pkg.go.dev/github.com/nicksnyder/go-i18n/v2/i18n)
@@ -140,6 +133,13 @@ If you have added new messages to your program:
- Look at the [code examples](https://github.com/nicksnyder/go-i18n/blob/main/i18n/example_test.go) and [tests](https://github.com/nicksnyder/go-i18n/blob/main/i18n/localizer_test.go).
- Look at an example [application](https://github.com/nicksnyder/go-i18n/tree/main/example).
+## Translations of this document
+
+Community translations of this document may be found in the [.github](.github) folder.
+
+These translations are maintained by the community, and are not maintained by the author of this project.
+They are not guaranteed to be accurate or up-to-date.
+
## License
go-i18n is available under the MIT license. See the [LICENSE](LICENSE) file for more info.
diff --git a/go.mod b/go.mod
index cf273ac7..6e0fba10 100644
--- a/go.mod
+++ b/go.mod
@@ -4,6 +4,6 @@ go 1.18
require (
github.com/BurntSushi/toml v1.4.0
- golang.org/x/text v0.17.0
+ golang.org/x/text v0.19.0
gopkg.in/yaml.v3 v3.0.1
)
diff --git a/go.sum b/go.sum
index e61e691b..c949e0dd 100644
--- a/go.sum
+++ b/go.sum
@@ -1,7 +1,7 @@
github.com/BurntSushi/toml v1.4.0 h1:kuoIxZQy2WRRk1pttg9asf+WVv6tWQuBNVmK8+nqPr0=
github.com/BurntSushi/toml v1.4.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
-golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc=
-golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
+golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM=
+golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
diff --git a/i18n/bundle_test.go b/i18n/bundle_test.go
index 917a7d3f..99706623 100644
--- a/i18n/bundle_test.go
+++ b/i18n/bundle_test.go
@@ -131,7 +131,7 @@ simple: simple translation
# Comment
detail:
- description: detail description
+ description: detail description
other: detail translation
# Comment
@@ -150,6 +150,47 @@ everything:
expectMessage(t, bundle, language.AmericanEnglish, "everything", everythingMessage)
}
+func TestInvalidYAML(t *testing.T) {
+ bundle := NewBundle(language.English)
+ bundle.RegisterUnmarshalFunc("yaml", yaml.Unmarshal)
+ _, err := bundle.ParseMessageFileBytes([]byte(`
+# Comment
+simple: simple translation
+
+# Comment
+detail:
+ description: detail description
+ other: detail translation
+
+# Comment
+everything:
+ description: everything description
+ zero: zero translation
+ one: one translation
+ two: two translation
+ few: few translation
+ many: many translation
+ other: other translation
+ garbage: something
+
+description: translation
+`), "en-US.yaml")
+
+ expectedErr := &mixedKeysError{
+ reservedKeys: []string{"description"},
+ unreservedKeys: []string{"detail", "everything", "simple"},
+ }
+ if err == nil {
+ t.Fatalf("expected error %#v; got nil", expectedErr)
+ }
+ if err.Error() != expectedErr.Error() {
+ t.Fatalf("expected error %q; got %q", expectedErr, err)
+ }
+ if c := len(bundle.messageTemplates); c > 0 {
+ t.Fatalf("expected no message templates in bundle; got %d", c)
+ }
+}
+
func TestTOML(t *testing.T) {
bundle := NewBundle(language.English)
bundle.RegisterUnmarshalFunc("toml", toml.Unmarshal)
diff --git a/i18n/localizer_test.go b/i18n/localizer_test.go
index f1c80524..5856ec6b 100644
--- a/i18n/localizer_test.go
+++ b/i18n/localizer_test.go
@@ -1,9 +1,11 @@
package i18n
import (
+ "errors"
"fmt"
"reflect"
"testing"
+ gotmpl "text/template"
"github.com/nicksnyder/go-i18n/v2/i18n/template"
"github.com/nicksnyder/go-i18n/v2/internal/plural"
@@ -648,6 +650,34 @@ func localizerTests() []localizerTest {
},
expectedErr: &MessageNotFoundErr{Tag: language.English, MessageID: "Hello"},
},
+ {
+ name: "use option missingkey=error with missing key",
+ defaultLanguage: language.English,
+ messages: map[language.Tag][]*Message{
+ language.English: {{ID: "Foo", Other: "Foo {{.bar}}"}},
+ },
+ acceptLangs: []string{"en"},
+ conf: &LocalizeConfig{
+ MessageID: "Foo",
+ TemplateData: map[string]string{},
+ TemplateParser: &template.TextParser{Option: "missingkey=error"},
+ },
+ expectedErr: gotmpl.ExecError{Name: "", Err: errors.New(`template: :1:6: executing "" at <.bar>: map has no entry for key "bar"`)},
+ },
+ {
+ name: "use option missingkey=default with missing key",
+ defaultLanguage: language.English,
+ messages: map[language.Tag][]*Message{
+ language.English: {{ID: "Foo", Other: "Foo {{.bar}}"}},
+ },
+ acceptLangs: []string{"en"},
+ conf: &LocalizeConfig{
+ MessageID: "Foo",
+ TemplateData: map[string]string{},
+ TemplateParser: &template.TextParser{Option: "missingkey=default"},
+ },
+ expectedLocalized: "Foo ",
+ },
}
}
@@ -663,7 +693,7 @@ func TestLocalizer_Localize(t *testing.T) {
check := func(localized string, err error) {
t.Helper()
if !reflect.DeepEqual(err, test.expectedErr) {
- t.Errorf("expected error %#v; got %#v", test.expectedErr, err)
+ t.Errorf("\nexpected error: %#v\n got error: %#v", test.expectedErr, err)
}
if localized != test.expectedLocalized {
t.Errorf("expected localized string %q; got %q", test.expectedLocalized, localized)
diff --git a/i18n/message.go b/i18n/message.go
index 99136a2a..30587693 100644
--- a/i18n/message.go
+++ b/i18n/message.go
@@ -2,6 +2,7 @@ package i18n
import (
"fmt"
+ "sort"
"strings"
)
@@ -175,47 +176,111 @@ func stringSubmap(k string, v interface{}, strdata map[string]string) error {
}
}
-// isMessage tells whether the given data is a message, or a map containing
-// nested messages.
-// A map is assumed to be a message if it contains any of the "reserved" keys:
-// "id", "description", "hash", "leftdelim", "rightdelim", "zero", "one", "two", "few", "many", "other"
-// with a string value.
-// e.g.,
+var reservedKeys = map[string]struct{}{
+ "id": {},
+ "description": {},
+ "hash": {},
+ "leftdelim": {},
+ "rightdelim": {},
+ "zero": {},
+ "one": {},
+ "two": {},
+ "few": {},
+ "many": {},
+ "other": {},
+ "translation": {},
+}
+
+func isReserved(key string, val any) bool {
+ if _, ok := reservedKeys[key]; ok {
+ if key == "translation" {
+ return true
+ }
+ if _, ok := val.(string); ok {
+ return true
+ }
+ }
+ return false
+}
+
+// isMessage returns true if v contains only message keys and false if it contains no message keys.
+// It returns an error if v contains both message and non-message keys.
// - {"message": {"description": "world"}} is a message
-// - {"message": {"description": "world", "foo": "bar"}} is a message ("foo" key is ignored)
-// - {"notmessage": {"description": {"hello": "world"}}} is not
-// - {"notmessage": {"foo": "bar"}} is not
-func isMessage(v interface{}) bool {
- reservedKeys := []string{"id", "description", "hash", "leftdelim", "rightdelim", "zero", "one", "two", "few", "many", "other"}
+// - {"error": {"description": "world", "foo": "bar"}} is an error
+// - {"notmessage": {"description": {"hello": "world"}}} is not a message
+// - {"notmessage": {"foo": "bar"}} is not a message
+func isMessage(v interface{}) (bool, error) {
switch data := v.(type) {
case nil, string:
- return true
+ return true, nil
case map[string]interface{}:
- for _, key := range reservedKeys {
+ reservedKeyCount := 0
+ for key := range reservedKeys {
val, ok := data[key]
- if !ok {
- continue
+ if ok && isReserved(key, val) {
+ reservedKeyCount++
}
- _, ok = val.(string)
- if !ok {
- continue
+ }
+ if reservedKeyCount == 0 {
+ return false, nil
+ }
+ if len(data) > reservedKeyCount {
+ reservedKeys := make([]string, 0, reservedKeyCount)
+ unreservedKeys := make([]string, 0, len(data)-reservedKeyCount)
+ for k, v := range data {
+ if isReserved(k, v) {
+ reservedKeys = append(reservedKeys, k)
+ } else {
+ unreservedKeys = append(unreservedKeys, k)
+ }
+ }
+ return false, &mixedKeysError{
+ reservedKeys: reservedKeys,
+ unreservedKeys: unreservedKeys,
}
- // v is a message if it contains a "reserved" key holding a string value
- return true
}
+ return true, nil
case map[interface{}]interface{}:
- for _, key := range reservedKeys {
+ reservedKeyCount := 0
+ for key := range reservedKeys {
val, ok := data[key]
- if !ok {
- continue
+ if ok && isReserved(key, val) {
+ reservedKeyCount++
}
- _, ok = val.(string)
- if !ok {
- continue
+ }
+ if reservedKeyCount == 0 {
+ return false, nil
+ }
+ if len(data) > reservedKeyCount {
+ reservedKeys := make([]string, 0, reservedKeyCount)
+ unreservedKeys := make([]string, 0, len(data)-reservedKeyCount)
+ for key, v := range data {
+ k, ok := key.(string)
+ if !ok {
+ unreservedKeys = append(unreservedKeys, fmt.Sprintf("%+v", key))
+ } else if isReserved(k, v) {
+ reservedKeys = append(reservedKeys, k)
+ } else {
+ unreservedKeys = append(unreservedKeys, k)
+ }
+ }
+ return false, &mixedKeysError{
+ reservedKeys: reservedKeys,
+ unreservedKeys: unreservedKeys,
}
- // v is a message if it contains a "reserved" key holding a string value
- return true
}
+ return true, nil
}
- return false
+ return false, nil
+}
+
+type mixedKeysError struct {
+ reservedKeys []string
+ unreservedKeys []string
+}
+
+func (e *mixedKeysError) Error() string {
+ sort.Strings(e.reservedKeys)
+ sort.Strings(e.unreservedKeys)
+ return fmt.Sprintf("reserved keys %v mixed with unreserved keys %v", e.reservedKeys, e.unreservedKeys)
}
diff --git a/i18n/parse.go b/i18n/parse.go
index 103d7bfa..fc7f7078 100644
--- a/i18n/parse.go
+++ b/i18n/parse.go
@@ -43,7 +43,12 @@ func ParseMessageFileBytes(buf []byte, path string, unmarshalFuncs map[string]Un
return nil, err
}
- if messageFile.Messages, err = recGetMessages(raw, isMessage(raw), true); err != nil {
+ m, err := isMessage(raw)
+ if err != nil {
+ return nil, err
+ }
+
+ if messageFile.Messages, err = recGetMessages(raw, m, true); err != nil {
return nil, err
}
@@ -105,7 +110,11 @@ func recGetMessages(raw interface{}, isMapMessage, isInitialCall bool) ([]*Messa
messages = make([]*Message, 0, len(data))
for _, data := range data {
// recursively scan slice items
- childMessages, err := recGetMessages(data, isMessage(data), false)
+ m, err := isMessage(data)
+ if err != nil {
+ return nil, err
+ }
+ childMessages, err := recGetMessages(data, m, false)
if err != nil {
return nil, err
}
@@ -127,7 +136,10 @@ func recGetMessages(raw interface{}, isMapMessage, isInitialCall bool) ([]*Messa
}
func addChildMessages(id string, data interface{}, messages []*Message) ([]*Message, error) {
- isChildMessage := isMessage(data)
+ isChildMessage, err := isMessage(data)
+ if err != nil {
+ return nil, err
+ }
childMessages, err := recGetMessages(data, isChildMessage, false)
if err != nil {
return nil, err
diff --git a/i18n/parse_test.go b/i18n/parse_test.go
index d0668170..29f05f4d 100644
--- a/i18n/parse_test.go
+++ b/i18n/parse_test.go
@@ -1,6 +1,7 @@
package i18n
import (
+ "errors"
"reflect"
"sort"
"testing"
@@ -32,6 +33,29 @@ func TestParseMessageFileBytes(t *testing.T) {
}},
},
},
+ {
+ name: "nested with reserved key",
+ file: `{"nested": {"description": {"other": "world"}}}`,
+ path: "en.json",
+ messageFile: &MessageFile{
+ Path: "en.json",
+ Tag: language.English,
+ Format: "json",
+ Messages: []*Message{{
+ ID: "nested.description",
+ Other: "world",
+ }},
+ },
+ },
+ {
+ name: "basic test reserved key top level",
+ file: `{"other": "world", "foo": "bar"}`,
+ path: "en.json",
+ err: &mixedKeysError{
+ reservedKeys: []string{"other"},
+ unreservedKeys: []string{"foo"},
+ },
+ },
{
name: "basic test with dot separator in key",
file: `{"prepended.hello": "world"}`,
@@ -97,14 +121,9 @@ func TestParseMessageFileBytes(t *testing.T) {
name: "basic test with description and dummy",
file: `{"notnested": {"description": "world", "dummy": "nothing"}}`,
path: "en.json",
- messageFile: &MessageFile{
- Path: "en.json",
- Tag: language.English,
- Format: "json",
- Messages: []*Message{{
- ID: "notnested",
- Description: "world",
- }},
+ err: &mixedKeysError{
+ reservedKeys: []string{"description"},
+ unreservedKeys: []string{"dummy"},
},
},
{
@@ -187,6 +206,29 @@ some-keys:
},
},
},
+ {
+ name: "YAML number key test",
+ file: `
+some-keys:
+ hello: world
+ 2: legit`,
+ path: "en.yaml",
+ unmarshalFuncs: map[string]UnmarshalFunc{"yaml": yaml.Unmarshal},
+ err: errors.New("expected key to be string but got 2"),
+ },
+ {
+ name: "YAML extra number key test",
+ file: `
+some-keys:
+ other: world
+ 2: legit`,
+ path: "en.yaml",
+ unmarshalFuncs: map[string]UnmarshalFunc{"yaml": yaml.Unmarshal},
+ err: &mixedKeysError{
+ reservedKeys: []string{"other"},
+ unreservedKeys: []string{"2"},
+ },
+ },
}
for _, testCase := range testCases {
t.Run(testCase.name, func(t *testing.T) {
@@ -210,13 +252,21 @@ some-keys:
t.Errorf("expected format %q; got %q", testCase.messageFile.Format, actual.Format)
}
if !equalMessages(actual.Messages, testCase.messageFile.Messages) {
- t.Errorf("expected %#v; got %#v", testCase.messageFile.Messages, actual.Messages)
+ t.Errorf("expected %#v; got %#v", deref(testCase.messageFile.Messages), deref(actual.Messages))
}
}
})
}
}
+func deref(mptrs []*Message) []Message {
+ messages := make([]Message, len(mptrs))
+ for i, m := range mptrs {
+ messages[i] = *m
+ }
+ return messages
+}
+
// equalMessages compares two slices of messages, ignoring private fields and order.
// Sorts both input slices, which are therefore modified by this function.
func equalMessages(m1, m2 []*Message) bool {
diff --git a/i18n/template/text_parser.go b/i18n/template/text_parser.go
index 76b4ba2d..e1a53858 100644
--- a/i18n/template/text_parser.go
+++ b/i18n/template/text_parser.go
@@ -37,7 +37,12 @@ func (te *TextParser) Parse(src, leftDelim, rightDelim string) (ParsedTemplate,
rightDelim = "}}"
}
- tmpl, err := template.New("").Delims(leftDelim, rightDelim).Funcs(te.Funcs).Parse(src)
+ option := "missingkey=default"
+ if te.Option != "" {
+ option = te.Option
+ }
+
+ tmpl, err := template.New("").Delims(leftDelim, rightDelim).Option(option).Funcs(te.Funcs).Parse(src)
if err != nil {
return nil, err
}