Skip to content

Commit

Permalink
Fix escaping
Browse files Browse the repository at this point in the history
  • Loading branch information
fullpipe committed Sep 16, 2024
1 parent 9114ee0 commit 8937284
Show file tree
Hide file tree
Showing 5 changed files with 101 additions and 3 deletions.
15 changes: 15 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,21 @@ tr.Trans(
)
```

### Escaping

Sometimes you need to print `{`, `'`, or `#`. You could escape them with `'` char.

```yaml
# translations/messages.en.yaml
escape: "'{foo} is ''{foo}''"
```
```go
tr.Trans("escape", mf.Arg("foo", "bar"))
// {foo} is 'bar'
```


## MessageFormat overview

### Placeholders
Expand Down
4 changes: 4 additions & 0 deletions message/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,10 @@ func Build(in parse.Message, lang language.Tag) (Evalable, error) {
}

func buildFragment(f parse.Fragment, lang language.Tag) (Evalable, error) {
if len(f.Escaped) > 0 {
return Content(f.Escaped[1:]), nil
}

if len(f.Text) > 0 {
return Content(f.Text), nil
}
Expand Down
48 changes: 48 additions & 0 deletions mf/translator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -418,6 +418,54 @@ func Test_translator_Trans(t *testing.T) {
"First step on the Moon on July 21, 1969.",
false,
},
{
"escaping quotes not required",
"foo ' {foo}.",
language.English,
[]TranslationArg{Arg("foo", "bar")},
"foo ' bar.",
false,
},
{
"escaping single curly brace",
"foo '{ {foo}.",
language.English,
[]TranslationArg{Arg("foo", "bar")},
"foo { bar.",
false,
},
{
"escaping single quote",
"foo '' {foo}.",
language.English,
[]TranslationArg{Arg("foo", "bar")},
"foo ' bar.",
false,
},
{
"escaping single quote before curly brace",
"foo ''{foo}''.",
language.English,
[]TranslationArg{Arg("foo", "bar")},
"foo 'bar'.",
false,
},
{
"escaping",
"foo '{ ''{foo} {num, plural, one {''#'' '# ' '{ one}, other {other}}.",
language.English,
[]TranslationArg{Arg("foo", "bar"), Arg("num", 1)},
"foo { 'bar '1' # ' { one.",
false,
},
{
"escaping",
"'{foo} is ''{foo}''",
language.English,
[]TranslationArg{Arg("foo", "bar")},
"{foo} is 'bar'",
false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
Expand Down
11 changes: 8 additions & 3 deletions parse/parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,8 @@ type Message struct {
}

type Fragment struct {
Text string `(@String | @SubMessageString)`
Escaped string `(@Escaped | @SubEscaped)`
Text string `| (@String | @SubMessageString | @Quote | @SubQuote)`
PlainArg *PlainArg `| @@`
Func *Func `| @@`
Expr *Expr `| @@`
Expand All @@ -42,7 +43,9 @@ type Fragment struct {
func NewParser() *participle.Parser[Message] {
def := lexer.MustStateful(lexer.Rules{
"Root": {
{Name: `String`, Pattern: `[^{]+`, Action: nil},
{Name: `Escaped`, Pattern: `'{|''`, Action: nil},
{Name: `Quote`, Pattern: `'`, Action: nil},
{Name: `String`, Pattern: `[^'{]+`, Action: nil},
{Name: `Expr`, Pattern: `{`, Action: lexer.Push("Expr")},
},
"Expr": {
Expand All @@ -55,8 +58,10 @@ func NewParser() *participle.Parser[Message] {
{Name: `SubMessage`, Pattern: `{`, Action: lexer.Push("SubMessage")},
},
"SubMessage": {
{Name: `SubMessageString`, Pattern: `[^{^}^#]+`, Action: nil},
{Name: `SubEscaped`, Pattern: `'{|''|'#|'}`, Action: nil},
{Name: `SubQuote`, Pattern: `'`, Action: nil},
{Name: `Octothorpe`, Pattern: `#`, Action: nil},
{Name: `SubMessageString`, Pattern: `[^{^}^#^']+`, Action: nil},
{Name: `Expr`, Pattern: `{`, Action: lexer.Push("Expr")},
{Name: `SubMessageEnd`, Pattern: `}`, Action: lexer.Pop()},
},
Expand Down
26 changes: 26 additions & 0 deletions parse/parser_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package parse

import (
"strings"
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func TestParser_Escaping(t *testing.T) {
parser := NewParser()

msg, err := parser.Parse("", strings.NewReader("foo '{ '' ' foo"))

require.NoError(t, err)
assert.Len(t, msg.Fragments, 7)

assert.Equal(t, &Fragment{Text: "foo "}, msg.Fragments[0])
assert.Equal(t, &Fragment{Escaped: "'{"}, msg.Fragments[1])
assert.Equal(t, &Fragment{Text: " "}, msg.Fragments[2])
assert.Equal(t, &Fragment{Escaped: "''"}, msg.Fragments[3])
assert.Equal(t, &Fragment{Text: " "}, msg.Fragments[4])
assert.Equal(t, &Fragment{Text: "'"}, msg.Fragments[5])
assert.Equal(t, &Fragment{Text: " foo"}, msg.Fragments[6])
}

0 comments on commit 8937284

Please sign in to comment.