Skip to content

Commit 7681d58

Browse files
authored
Refactor locale number (#24134)
Before, the `GiteaLocaleNumber.js` was just written as a a drop-in replacement for old `js-pretty-number`. Actually, we can use Golang's `text` package to format. This PR partially completes the TODOs in `GiteaLocaleNumber.js`: > if we have complete backend locale support (eg: Golang "x/text" package), we can drop this component. > tooltip: only 2 usages of this, we can replace it with Golang's "x/text/number" package in the future. This PR also helps #24131 Screenshots: <details> ![image](https://user-images.githubusercontent.com/2114189/232179420-b1b9974b-9d96-4408-b209-b80182c8b359.png) ![image](https://user-images.githubusercontent.com/2114189/232179416-14f36aa0-3f3e-4ac9-b366-7bd3a4464a11.png) </details>
1 parent be7cd73 commit 7681d58

File tree

19 files changed

+118
-106
lines changed

19 files changed

+118
-106
lines changed

modules/charset/escape_test.go

+3-11
Original file line numberDiff line numberDiff line change
@@ -132,18 +132,10 @@ then resh (ר), and finally heh (ה) (which should appear leftmost).`,
132132
},
133133
}
134134

135-
type nullLocale struct{}
136-
137-
func (nullLocale) Language() string { return "" }
138-
func (nullLocale) Tr(key string, _ ...interface{}) string { return key }
139-
func (nullLocale) TrN(cnt interface{}, key1, keyN string, args ...interface{}) string { return "" }
140-
141-
var _ (translation.Locale) = nullLocale{}
142-
143135
func TestEscapeControlString(t *testing.T) {
144136
for _, tt := range escapeControlTests {
145137
t.Run(tt.name, func(t *testing.T) {
146-
status, result := EscapeControlString(tt.text, nullLocale{})
138+
status, result := EscapeControlString(tt.text, &translation.MockLocale{})
147139
if !reflect.DeepEqual(*status, tt.status) {
148140
t.Errorf("EscapeControlString() status = %v, wanted= %v", status, tt.status)
149141
}
@@ -179,7 +171,7 @@ func TestEscapeControlReader(t *testing.T) {
179171
t.Run(tt.name, func(t *testing.T) {
180172
input := strings.NewReader(tt.text)
181173
output := &strings.Builder{}
182-
status, err := EscapeControlReader(input, output, nullLocale{})
174+
status, err := EscapeControlReader(input, output, &translation.MockLocale{})
183175
result := output.String()
184176
if err != nil {
185177
t.Errorf("EscapeControlReader(): err = %v", err)
@@ -201,5 +193,5 @@ func TestEscapeControlReader_panic(t *testing.T) {
201193
for i := 0; i < 6826; i++ {
202194
bs = append(bs, []byte("—")...)
203195
}
204-
_, _ = EscapeControlString(string(bs), nullLocale{})
196+
_, _ = EscapeControlString(string(bs), &translation.MockLocale{})
205197
}

modules/csv/csv_test.go

+2-15
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import (
1313

1414
"code.gitea.io/gitea/modules/git"
1515
"code.gitea.io/gitea/modules/markup"
16+
"code.gitea.io/gitea/modules/translation"
1617

1718
"github.com/stretchr/testify/assert"
1819
)
@@ -550,20 +551,6 @@ a|"he said, ""here I am"""`,
550551
}
551552
}
552553

553-
type mockLocale struct{}
554-
555-
func (l mockLocale) Language() string {
556-
return "en"
557-
}
558-
559-
func (l mockLocale) Tr(s string, _ ...interface{}) string {
560-
return s
561-
}
562-
563-
func (l mockLocale) TrN(_cnt interface{}, key1, _keyN string, _args ...interface{}) string {
564-
return key1
565-
}
566-
567554
func TestFormatError(t *testing.T) {
568555
cases := []struct {
569556
err error
@@ -591,7 +578,7 @@ func TestFormatError(t *testing.T) {
591578
}
592579

593580
for n, c := range cases {
594-
message, err := FormatError(c.err, mockLocale{})
581+
message, err := FormatError(c.err, &translation.MockLocale{})
595582
if c.expectsError {
596583
assert.Error(t, err, "case %d: expected an error to be returned", n)
597584
} else {

modules/templates/helper.go

-7
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,6 @@ func NewFuncMap() []template.FuncMap {
132132
// -----------------------------------------------------------------
133133
// time / number / format
134134
"FileSize": base.FileSize,
135-
"LocaleNumber": LocaleNumber,
136135
"CountFmt": base.FormatNumberSI,
137136
"TimeSince": timeutil.TimeSince,
138137
"TimeSinceUnix": timeutil.TimeSinceUnix,
@@ -782,12 +781,6 @@ func mirrorRemoteAddress(ctx context.Context, m *repo_model.Repository, remoteNa
782781
return a
783782
}
784783

785-
// LocaleNumber renders a number with a Custom Element, browser will render it with a locale number
786-
func LocaleNumber(v interface{}) template.HTML {
787-
num, _ := util.ToInt64(v)
788-
return template.HTML(fmt.Sprintf(`<gitea-locale-number data-number="%d">%d</gitea-locale-number>`, num, num))
789-
}
790-
791784
// Eval the expression and return the result, see the comment of eval.Expr for details.
792785
// To use this helper function in templates, pass each token as a separate parameter.
793786
//

modules/test/context_tests.go

+2-15
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import (
1818
user_model "code.gitea.io/gitea/models/user"
1919
"code.gitea.io/gitea/modules/context"
2020
"code.gitea.io/gitea/modules/git"
21+
"code.gitea.io/gitea/modules/translation"
2122
"code.gitea.io/gitea/modules/web/middleware"
2223

2324
chi "github.com/go-chi/chi/v5"
@@ -34,7 +35,7 @@ func MockContext(t *testing.T, path string) *context.Context {
3435
Values: make(url.Values),
3536
},
3637
Resp: context.NewResponse(resp),
37-
Locale: &mockLocale{},
38+
Locale: &translation.MockLocale{},
3839
}
3940
defer ctx.Close()
4041

@@ -91,20 +92,6 @@ func LoadGitRepo(t *testing.T, ctx *context.Context) {
9192
assert.NoError(t, err)
9293
}
9394

94-
type mockLocale struct{}
95-
96-
func (l mockLocale) Language() string {
97-
return "en"
98-
}
99-
100-
func (l mockLocale) Tr(s string, _ ...interface{}) string {
101-
return s
102-
}
103-
104-
func (l mockLocale) TrN(_cnt interface{}, key1, _keyN string, _args ...interface{}) string {
105-
return key1
106-
}
107-
10895
type mockResponseWriter struct {
10996
httptest.ResponseRecorder
11097
size int

modules/translation/mock.go

+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
// Copyright 2020 The Gitea Authors. All rights reserved.
2+
// SPDX-License-Identifier: MIT
3+
4+
package translation
5+
6+
import "fmt"
7+
8+
// MockLocale provides a mocked locale without any translations
9+
type MockLocale struct{}
10+
11+
var _ Locale = (*MockLocale)(nil)
12+
13+
func (l MockLocale) Language() string {
14+
return "en"
15+
}
16+
17+
func (l MockLocale) Tr(s string, _ ...interface{}) string {
18+
return s
19+
}
20+
21+
func (l MockLocale) TrN(_cnt interface{}, key1, _keyN string, _args ...interface{}) string {
22+
return key1
23+
}
24+
25+
func (l MockLocale) PrettyNumber(v any) string {
26+
return fmt.Sprint(v)
27+
}

modules/translation/translation.go

+25-5
Original file line numberDiff line numberDiff line change
@@ -15,17 +15,20 @@ import (
1515
"code.gitea.io/gitea/modules/translation/i18n"
1616

1717
"golang.org/x/text/language"
18+
"golang.org/x/text/message"
19+
"golang.org/x/text/number"
1820
)
1921

2022
type contextKey struct{}
2123

22-
var ContextKey interface{} = &contextKey{}
24+
var ContextKey any = &contextKey{}
2325

2426
// Locale represents an interface to translation
2527
type Locale interface {
2628
Language() string
27-
Tr(string, ...interface{}) string
28-
TrN(cnt interface{}, key1, keyN string, args ...interface{}) string
29+
Tr(string, ...any) string
30+
TrN(cnt any, key1, keyN string, args ...any) string
31+
PrettyNumber(v any) string
2932
}
3033

3134
// LangType represents a lang type
@@ -135,6 +138,7 @@ func Match(tags ...language.Tag) language.Tag {
135138
type locale struct {
136139
i18n.Locale
137140
Lang, LangName string // these fields are used directly in templates: .i18n.Lang
141+
msgPrinter *message.Printer
138142
}
139143

140144
// NewLocale return a locale
@@ -147,13 +151,24 @@ func NewLocale(lang string) Locale {
147151
langName := "unknown"
148152
if l, ok := allLangMap[lang]; ok {
149153
langName = l.Name
154+
} else if len(setting.Langs) > 0 {
155+
lang = setting.Langs[0]
156+
langName = setting.Names[0]
150157
}
158+
151159
i18nLocale, _ := i18n.GetLocale(lang)
152-
return &locale{
160+
l := &locale{
153161
Locale: i18nLocale,
154162
Lang: lang,
155163
LangName: langName,
156164
}
165+
if langTag, err := language.Parse(lang); err != nil {
166+
log.Error("Failed to parse language tag from name %q: %v", l.Lang, err)
167+
l.msgPrinter = message.NewPrinter(language.English)
168+
} else {
169+
l.msgPrinter = message.NewPrinter(langTag)
170+
}
171+
return l
157172
}
158173

159174
func (l *locale) Language() string {
@@ -199,7 +214,7 @@ var trNLangRules = map[string]func(int64) int{
199214
}
200215

201216
// TrN returns translated message for plural text translation
202-
func (l *locale) TrN(cnt interface{}, key1, keyN string, args ...interface{}) string {
217+
func (l *locale) TrN(cnt any, key1, keyN string, args ...any) string {
203218
var c int64
204219
if t, ok := cnt.(int); ok {
205220
c = int64(t)
@@ -223,3 +238,8 @@ func (l *locale) TrN(cnt interface{}, key1, keyN string, args ...interface{}) st
223238
}
224239
return l.Tr(keyN, args...)
225240
}
241+
242+
func (l *locale) PrettyNumber(v any) string {
243+
// TODO: this mechanism is not good enough, the complete solution is to switch the translation system to ICU message format
244+
return l.msgPrinter.Sprintf("%v", number.Decimal(v))
245+
}
+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
// Copyright 2023 The Gitea Authors. All rights reserved.
2+
// SPDX-License-Identifier: MIT
3+
4+
package translation
5+
6+
import (
7+
"testing"
8+
9+
"code.gitea.io/gitea/modules/translation/i18n"
10+
11+
"github.com/stretchr/testify/assert"
12+
)
13+
14+
func TestPrettyNumber(t *testing.T) {
15+
// TODO: make this package friendly to testing
16+
17+
i18n.ResetDefaultLocales()
18+
19+
allLangMap = make(map[string]*LangType)
20+
allLangMap["id-ID"] = &LangType{Lang: "id-ID", Name: "Bahasa Indonesia"}
21+
22+
l := NewLocale("id-ID")
23+
assert.EqualValues(t, "1.000.000", l.PrettyNumber(1000000))
24+
25+
l = NewLocale("nosuch")
26+
assert.EqualValues(t, "1,000,000", l.PrettyNumber(1000000))
27+
}

templates/devtest/gitea-ui.tmpl

+7-7
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,13 @@
1515

1616
<div>
1717
<h1>LocaleNumber</h1>
18-
<div>{{LocaleNumber 1}}</div>
19-
<div>{{LocaleNumber 12}}</div>
20-
<div>{{LocaleNumber 123}}</div>
21-
<div>{{LocaleNumber 1234}}</div>
22-
<div>{{LocaleNumber 12345}}</div>
23-
<div>{{LocaleNumber 123456}}</div>
24-
<div>{{LocaleNumber 1234567}}</div>
18+
<div>{{.locale.PrettyNumber 1}}</div>
19+
<div>{{.locale.PrettyNumber 12}}</div>
20+
<div>{{.locale.PrettyNumber 123}}</div>
21+
<div>{{.locale.PrettyNumber 1234}}</div>
22+
<div>{{.locale.PrettyNumber 12345}}</div>
23+
<div>{{.locale.PrettyNumber 123456}}</div>
24+
<div>{{.locale.PrettyNumber 1234567}}</div>
2525
</div>
2626

2727
<div>

templates/projects/list.tmpl

+4-4
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,11 @@
1313
<div class="ui compact tiny menu">
1414
<a class="item{{if not .IsShowClosed}} active{{end}}" href="{{$.Link}}?state=open">
1515
{{svg "octicon-project-symlink" 16 "gt-mr-3"}}
16-
{{LocaleNumber .OpenCount}}&nbsp;{{.locale.Tr "repo.issues.open_title"}}
16+
{{.locale.PrettyNumber .OpenCount}}&nbsp;{{.locale.Tr "repo.issues.open_title"}}
1717
</a>
1818
<a class="item{{if .IsShowClosed}} active{{end}}" href="{{$.Link}}?state=closed">
1919
{{svg "octicon-check" 16 "gt-mr-3"}}
20-
{{LocaleNumber .ClosedCount}}&nbsp;{{.locale.Tr "repo.issues.closed_title"}}
20+
{{.locale.PrettyNumber .ClosedCount}}&nbsp;{{.locale.Tr "repo.issues.closed_title"}}
2121
</a>
2222
</div>
2323

@@ -46,9 +46,9 @@
4646
{{end}}
4747
<span class="issue-stats">
4848
{{svg "octicon-issue-opened" 16 "gt-mr-3"}}
49-
{{LocaleNumber .NumOpenIssues}}&nbsp;{{$.locale.Tr "repo.issues.open_title"}}
49+
{{$.locale.PrettyNumber .NumOpenIssues}}&nbsp;{{$.locale.Tr "repo.issues.open_title"}}
5050
{{svg "octicon-check" 16 "gt-mr-3"}}
51-
{{LocaleNumber .NumClosedIssues}}&nbsp;{{$.locale.Tr "repo.issues.closed_title"}}
51+
{{$.locale.PrettyNumber .NumClosedIssues}}&nbsp;{{$.locale.Tr "repo.issues.closed_title"}}
5252
</span>
5353
</div>
5454
{{if and $.CanWriteProjects (not $.Repository.IsArchived)}}

templates/repo/issue/milestones.tmpl

+4-4
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,11 @@
1818
<div class="ui compact tiny menu">
1919
<a class="item{{if not .IsShowClosed}} active{{end}}" href="{{.RepoLink}}/milestones?state=open&q={{$.Keyword}}">
2020
{{svg "octicon-milestone" 16 "gt-mr-3"}}
21-
{{LocaleNumber .OpenCount}}&nbsp;{{.locale.Tr "repo.issues.open_title"}}
21+
{{.locale.PrettyNumber .OpenCount}}&nbsp;{{.locale.Tr "repo.issues.open_title"}}
2222
</a>
2323
<a class="item{{if .IsShowClosed}} active{{end}}" href="{{.RepoLink}}/milestones?state=closed&q={{$.Keyword}}">
2424
{{svg "octicon-check" 16 "gt-mr-3"}}
25-
{{LocaleNumber .ClosedCount}}&nbsp;{{.locale.Tr "repo.issues.closed_title"}}
25+
{{.locale.PrettyNumber .ClosedCount}}&nbsp;{{.locale.Tr "repo.issues.closed_title"}}
2626
</a>
2727
</div>
2828
</div>
@@ -84,9 +84,9 @@
8484
{{end}}
8585
<span class="issue-stats">
8686
{{svg "octicon-issue-opened" 16 "gt-mr-3"}}
87-
{{LocaleNumber .NumOpenIssues}}&nbsp;{{$.locale.Tr "repo.issues.open_title"}}
87+
{{$.locale.PrettyNumber .NumOpenIssues}}&nbsp;{{$.locale.Tr "repo.issues.open_title"}}
8888
{{svg "octicon-check" 16 "gt-mr-3"}}
89-
{{LocaleNumber .NumClosedIssues}}&nbsp;{{$.locale.Tr "repo.issues.closed_title"}}
89+
{{$.locale.PrettyNumber .NumClosedIssues}}&nbsp;{{$.locale.Tr "repo.issues.closed_title"}}
9090
{{if .TotalTrackedTime}}{{svg "octicon-clock"}} {{.TotalTrackedTime|Sec2Time}}{{end}}
9191
{{if .UpdatedUnix}}{{svg "octicon-clock"}} {{$.locale.Tr "repo.milestones.update_ago" (TimeSinceUnix .UpdatedUnix $.locale) | Safe}}{{end}}
9292
</span>

templates/repo/issue/openclose.tmpl

+2-2
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,10 @@
55
{{else}}
66
{{svg "octicon-issue-opened" 16 "gt-mr-3"}}
77
{{end}}
8-
{{LocaleNumber .IssueStats.OpenCount}}&nbsp;{{.locale.Tr "repo.issues.open_title"}}
8+
{{.locale.PrettyNumber .IssueStats.OpenCount}}&nbsp;{{.locale.Tr "repo.issues.open_title"}}
99
</a>
1010
<a class="{{if .IsShowClosed}}active {{end}}item" href="{{$.Link}}?q={{$.Keyword}}&type={{.ViewType}}&sort={{$.SortType}}&state=closed&labels={{.SelectLabels}}&milestone={{.MilestoneID}}&project={{.ProjectID}}&assignee={{.AssigneeID}}&poster={{.PosterID}}">
1111
{{svg "octicon-check" 16 "gt-mr-3"}}
12-
{{LocaleNumber .IssueStats.ClosedCount}}&nbsp;{{.locale.Tr "repo.issues.closed_title"}}
12+
{{.locale.PrettyNumber .IssueStats.ClosedCount}}&nbsp;{{.locale.Tr "repo.issues.closed_title"}}
1313
</a>
1414
</div>

templates/repo/projects/list.tmpl

+4-4
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,11 @@
1515
<div class="ui compact tiny menu">
1616
<a class="item{{if not .IsShowClosed}} active{{end}}" href="{{.RepoLink}}/projects?state=open">
1717
{{svg "octicon-project" 16 "gt-mr-3"}}
18-
{{LocaleNumber .OpenCount}}&nbsp;{{.locale.Tr "repo.issues.open_title"}}
18+
{{.locale.PrettyNumber .OpenCount}}&nbsp;{{.locale.Tr "repo.issues.open_title"}}
1919
</a>
2020
<a class="item{{if .IsShowClosed}} active{{end}}" href="{{.RepoLink}}/projects?state=closed">
2121
{{svg "octicon-check" 16 "gt-mr-3"}}
22-
{{LocaleNumber .ClosedCount}}&nbsp;{{.locale.Tr "repo.issues.closed_title"}}
22+
{{.locale.PrettyNumber .ClosedCount}}&nbsp;{{.locale.Tr "repo.issues.closed_title"}}
2323
</a>
2424
</div>
2525

@@ -48,9 +48,9 @@
4848
{{end}}
4949
<span class="issue-stats">
5050
{{svg "octicon-issue-opened" 16 "gt-mr-3"}}
51-
{{LocaleNumber .NumOpenIssues}}&nbsp;{{$.locale.Tr "repo.issues.open_title"}}
51+
{{.locale.PrettyNumber .NumOpenIssues}}&nbsp;{{$.locale.Tr "repo.issues.open_title"}}
5252
{{svg "octicon-check" 16 "gt-mr-3"}}
53-
{{LocaleNumber .NumClosedIssues}}&nbsp;{{$.locale.Tr "repo.issues.closed_title"}}
53+
{{.locale.PrettyNumber .NumClosedIssues}}&nbsp;{{$.locale.Tr "repo.issues.closed_title"}}
5454
</span>
5555
</div>
5656
{{if and $.CanWriteProjects (not $.Repository.IsArchived)}}

templates/repo/release/list.tmpl

+2-2
Original file line numberDiff line numberDiff line change
@@ -161,9 +161,9 @@
161161
<li>
162162
<span class="ui text middle aligned right">
163163
<span class="ui text grey">{{.Size | FileSize}}</span>
164-
<gitea-locale-number data-number-in-tooltip="{{dict "message" ($.locale.Tr "repo.release.download_count") "number" .DownloadCount | Json}}">
164+
<span data-tooltip-content="{{$.locale.Tr "repo.release.download_count" ($.locale.PrettyNumber .DownloadCount)}}">
165165
{{svg "octicon-info"}}
166-
</gitea-locale-number>
166+
</span>
167167
</span>
168168
<a target="_blank" rel="noopener noreferrer" href="{{.DownloadURL}}">
169169
<strong>{{svg "octicon-package" 16 "gt-mr-2"}}{{.Name}}</strong>

0 commit comments

Comments
 (0)