Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix bugs of localized numbers #2980

Merged
Merged
Show file tree
Hide file tree
Changes from 2 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
41 changes: 28 additions & 13 deletions api/v2/formatting/handler_formatting.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,11 @@
"sgtnserver/internal/logger"
"sgtnserver/internal/sgtnerror"
"sgtnserver/modules/cldr"
"sgtnserver/modules/cldr/localeutil"
"sgtnserver/modules/cldr/cldrservice"
"sgtnserver/modules/formatting"

"github.com/gin-gonic/gin"
jsoniter "github.com/json-iterator/go"
)

// GetLocalizedDate godoc
Expand Down Expand Up @@ -73,12 +74,17 @@
return
}

numbers, err := localeutil.GetPatternData(logger.NewContext(c, c.MustGet(api.LoggerKey)), params.Locale, cldr.PatternNumbers)
ctxWithLog := logger.NewContext(c, c.MustGet(api.LoggerKey))
patterData, cldrLocale, err := cldrservice.GetPatternByLocale(ctxWithLog, params.Locale, cldr.PatternNumbers, "")
if err != nil {
api.AbortWithError(c, sgtnerror.StatusBadRequest.WrapErrorWithMessage(err, "invalid Locale '%s'", params.Locale))
patterData, cldrLocale, err = cldrservice.GetPatternByLocale(ctxWithLog, cldr.EnLocale, cldr.PatternNumbers, "")
}

Check warning on line 81 in api/v2/formatting/handler_formatting.go

View check run for this annotation

Codecov / codecov/patch

api/v2/formatting/handler_formatting.go#L80-L81

Added lines #L80 - L81 were not covered by tests
if err != nil {
api.AbortWithError(c, sgtnerror.StatusBadRequest.WrapErrorWithMessage(err, "fail to get pattern data"))

Check warning on line 83 in api/v2/formatting/handler_formatting.go

View check run for this annotation

Codecov / codecov/patch

api/v2/formatting/handler_formatting.go#L83

Added line #L83 was not covered by tests
return
}

numbers := patterData["numbers"].(jsoniter.Any)
decimalFormats := numbers.Get("numberFormats", "decimalFormats").ToString()
numberSymbols := numbers.Get("numberSymbols")
decimalSign := numberSymbols.Get("decimal").ToString()
Expand All @@ -93,33 +99,42 @@

bigFloat, _, _ := big.ParseFloat(params.Number, 10, 0, big.ToNearestAway)
roundedNumberStr := bigFloat.Text('f', params.Scale)
sign := ""
if roundedNumberStr[0] == '-' {
sign = "-"
roundedNumberStr = roundedNumberStr[1:]
}

decimalPointIndex := strings.LastIndex(roundedNumberStr, ".")
if decimalPointIndex == -1 {
decimalPointIndex = len(roundedNumberStr)
}

var sb strings.Builder
i := (decimalPointIndex - len(stringSubmatch[2])) % groupLength
if i != 0 {
sb.WriteString(roundedNumberStr[:i])
if !(i == 1 && (roundedNumberStr[0] == '+' || roundedNumberStr[0] == '-')) {
sb.WriteString(sign)
firstPartLength := decimalPointIndex - len(stringSubmatch[2]) // Remove the decimal part and the last group of the integer part
if firstPartLength > 0 {
i := firstPartLength % groupLength // the first group length
if i != 0 {
sb.WriteString(roundedNumberStr[:i])
sb.WriteString(groupSign)
}
}
for ; i < decimalPointIndex-len(stringSubmatch[2]); i += groupLength {
sb.WriteString(roundedNumberStr[i : i+groupLength])
sb.WriteString(groupSign)
for ; i < firstPartLength; i += groupLength {
sb.WriteString(roundedNumberStr[i : i+groupLength])
sb.WriteString(groupSign)
}
sb.WriteString(roundedNumberStr[i:decimalPointIndex])
} else {
sb.WriteString(roundedNumberStr[:decimalPointIndex])
}

sb.WriteString(roundedNumberStr[i:decimalPointIndex])
if params.Scale > 0 {
sb.WriteString(decimalSign)
sb.WriteString(roundedNumberStr[decimalPointIndex+1:])
}

data := NumberResp{
Locale: params.Locale,
Locale: cldrLocale,
Number: params.Number,
Scale: strconv.FormatInt(int64(params.Scale), 10),
FormattedNumber: sb.String()}
Expand Down
4 changes: 4 additions & 0 deletions modules/cldr/constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,3 +48,7 @@
TimeZoneName = "TimeZoneName"
LocaleCities = "LocaleCities"
)

const (
EnLocale = "en"
Dismissed Show dismissed Hide dismissed
)
51 changes: 51 additions & 0 deletions tests/formatting_api_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/*
* Copyright 2024 VMware, Inc.
* SPDX-License-Identifier: EPL-2.0
*/

package tests

import (
"net/http"
"testing"

_ "sgtnserver/api/v2/formatting"

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

const LocalizedNumberURL = BaseURL + "/formatting/number/localizedNumber"

func TestLocalizedNumber(t *testing.T) {
e := CreateHTTPExpect(t, GinTestEngine)

data := []struct {
desc string
locale string
number string
scale int
expected string
wantedCode int
}{
{desc: "char number in the first group is equal to group length, scale and actual decimal part is 0, negative number", number: "-123456789", scale: 0, locale: "en-US", expected: `{"response":{"code":200,"message":"OK"},"data":{"locale":"en","number":"-123456789","scale":"0","formattedNumber":"-123,456,789"}}`, wantedCode: http.StatusOK},
{desc: "char number in the first group is less than group length, scale is large than actual", number: "23456789.123456", scale: 10, locale: "en-US", expected: `{"response":{"code":200,"message":"OK"},"data":{"locale":"en","number":"23456789.123456","scale":"10","formattedNumber":"23,456,789.1234560000"}}`, wantedCode: http.StatusOK},
{desc: "the first group is the only group, scale is less than actual", number: "789.123456", scale: 2, locale: "en-US", expected: `{"response":{"code":200,"message":"OK"},"data":{"locale":"en","number":"789.123456","scale":"2","formattedNumber":"789.12"}}`, wantedCode: http.StatusOK},
{desc: "the first group is the only group and length is less than group length, scale is 0", number: "89.123456", scale: 0, locale: "en-US", expected: ` {"response":{"code":200,"message":"OK"},"data":{"locale":"en","number":"89.123456","scale":"0","formattedNumber":"89"}}`, wantedCode: http.StatusOK},
{desc: "the first group is the only group and length is less than group length, decimal part is 0, scale is 2, negative number", number: "-89", scale: 2, locale: "en-US", expected: `{"response":{"code":200,"message":"OK"},"data":{"locale":"en","number":"-89","scale":"2","formattedNumber":"-89.00"}}`, wantedCode: http.StatusOK},
{desc: "integer part is zero", number: "0", scale: 2, locale: "en-US", expected: `{"response":{"code":200,"message":"OK"},"data":{"locale":"en","number":"0","scale":"2","formattedNumber":"0.00"}}`, wantedCode: http.StatusOK},

{desc: "locale 'bn', char number in the first group is equal to group length, scale and actual decimal part is 0, negative number", number: "-123456789", scale: 0, locale: "bn", expected: `{"response":{"code":200,"message":"OK"},"data":{"locale":"bn","number":"-123456789","scale":"0","formattedNumber":"-12,34,56,789"}}`, wantedCode: http.StatusOK},
{desc: "locale 'bn', char number in the first group is less than group length, scale is large than actual, negative number", number: "-23456789.123456", scale: 10, locale: "bn", expected: `{"response":{"code":200,"message":"OK"},"data":{"locale":"bn","number":"-23456789.123456","scale":"10","formattedNumber":"-2,34,56,789.1234560000"}}`, wantedCode: http.StatusOK},
{desc: "locale 'bn', the first group is the only group", number: "789", scale: 2, locale: "bn", expected: `{"response":{"code":200,"message":"OK"},"data":{"locale":"bn","number":"789","scale":"2","formattedNumber":"789.00"}}`, wantedCode: http.StatusOK},
}

for _, d := range data {
d := d

t.Run(d.desc, func(t *testing.T) {
resp := e.GET(LocalizedNumberURL, Name, Version).WithQuery("locale", d.locale).WithQuery("number", d.number).WithQuery("scale", d.scale).Expect()
resp.Status(d.wantedCode)
assert.JSONEq(t, d.expected, resp.Body().Raw())
})
}
}
Loading