]> source.dussan.org Git - gitea.git/commitdiff
Introduce GiteaLocaleNumber custom element to handle number localization on pages...
authorwxiaoguang <wxiaoguang@gmail.com>
Mon, 3 Apr 2023 16:58:09 +0000 (00:58 +0800)
committerGitHub <noreply@github.com>
Mon, 3 Apr 2023 16:58:09 +0000 (12:58 -0400)
Follow #21429 & #22861

Use `<gitea-locale-number>` instead of backend `PrettyNumber`. All old
`PrettyNumber` related functions are removed. A lot of code could be
simplified.

And some functions haven't been used for long time (dead code), so they
are also removed by the way (eg: `SplitStringAtRuneN`, `Dedent`)

This PR only tries to improve the `PrettyNumber` rendering problem, it
doesn't touch the "plural" problem.

Screenshot:

![image](https://user-images.githubusercontent.com/2114189/229290804-1f63db65-1e34-4a54-84ba-e00b44331b17.png)

![image](https://user-images.githubusercontent.com/2114189/229290911-c88dea00-b11d-48dd-accb-9f52edd73ce4.png)

24 files changed:
modules/base/tool.go
modules/base/tool_test.go
modules/templates/helper.go
modules/util/truncate.go
modules/util/truncate_test.go
modules/util/util.go
modules/util/util_test.go
templates/projects/list.tmpl
templates/repo/issue/milestones.tmpl
templates/repo/issue/openclose.tmpl
templates/repo/projects/list.tmpl
templates/repo/release/list.tmpl
templates/repo/release/new.tmpl
templates/repo/sub_menu.tmpl
templates/user/dashboard/issues.tmpl
templates/user/dashboard/milestones.tmpl
web_src/js/features/formatting.js
web_src/js/utils.js
web_src/js/utils.test.js
web_src/js/webcomponents/GiteaLocaleNumber.js [new file with mode: 0644]
web_src/js/webcomponents/GiteaOriginUrl.js
web_src/js/webcomponents/README.md
web_src/js/webcomponents/webcomponents.js [new file with mode: 0644]
webpack.config.js

index 94f19576b48d7ca327e06a2f2e1835a1d07c8459..bd3a8458eeb291671d1bf28a0a2b3858b4018383 100644 (file)
@@ -22,7 +22,6 @@ import (
        "code.gitea.io/gitea/modules/git"
        "code.gitea.io/gitea/modules/log"
        "code.gitea.io/gitea/modules/setting"
-       "code.gitea.io/gitea/modules/util"
 
        "github.com/dustin/go-humanize"
        "github.com/minio/sha256-simd"
@@ -142,12 +141,6 @@ func FileSize(s int64) string {
        return humanize.IBytes(uint64(s))
 }
 
-// PrettyNumber produces a string form of the given number in base 10 with
-// commas after every three orders of magnitude
-func PrettyNumber(i interface{}) string {
-       return humanize.Comma(util.NumberIntoInt64(i))
-}
-
 // Subtract deals with subtraction of all types of number.
 func Subtract(left, right interface{}) interface{} {
        var rleft, rright int64
index 81f4b464e6d3682c23df494fc5655752f8628b20..33677a910cc22a81ff366e3c68a3ef0e6d9c9382 100644 (file)
@@ -114,13 +114,6 @@ func TestFileSize(t *testing.T) {
        assert.Equal(t, "2.0 EiB", FileSize(size))
 }
 
-func TestPrettyNumber(t *testing.T) {
-       assert.Equal(t, "23,342,432", PrettyNumber(23342432))
-       assert.Equal(t, "23,342,432", PrettyNumber(int32(23342432)))
-       assert.Equal(t, "0", PrettyNumber(0))
-       assert.Equal(t, "-100,000", PrettyNumber(-100000))
-}
-
 func TestSubtract(t *testing.T) {
        toFloat64 := func(n interface{}) float64 {
                switch v := n.(type) {
index a8343428dc19bcd6d650a3d3302177b8730fb311..54c85863bd8276bd2a26093e848ec81444c1009f 100644 (file)
@@ -19,7 +19,6 @@ import (
        "reflect"
        "regexp"
        "runtime"
-       "strconv"
        "strings"
        texttmpl "text/template"
        "time"
@@ -112,18 +111,17 @@ func NewFuncMap() []template.FuncMap {
                "IsShowFullName": func() bool {
                        return setting.UI.DefaultShowFullName
                },
-               "Safe":           Safe,
-               "SafeJS":         SafeJS,
-               "JSEscape":       JSEscape,
-               "Str2html":       Str2html,
-               "TimeSince":      timeutil.TimeSince,
-               "TimeSinceUnix":  timeutil.TimeSinceUnix,
-               "FileSize":       base.FileSize,
-               "PrettyNumber":   base.PrettyNumber,
-               "JsPrettyNumber": JsPrettyNumber,
-               "Subtract":       base.Subtract,
-               "EntryIcon":      base.EntryIcon,
-               "MigrationIcon":  MigrationIcon,
+               "Safe":          Safe,
+               "SafeJS":        SafeJS,
+               "JSEscape":      JSEscape,
+               "Str2html":      Str2html,
+               "TimeSince":     timeutil.TimeSince,
+               "TimeSinceUnix": timeutil.TimeSinceUnix,
+               "FileSize":      base.FileSize,
+               "LocaleNumber":  LocaleNumber,
+               "Subtract":      base.Subtract,
+               "EntryIcon":     base.EntryIcon,
+               "MigrationIcon": MigrationIcon,
                "Add": func(a ...int) int {
                        sum := 0
                        for _, val := range a {
@@ -410,62 +408,9 @@ func NewFuncMap() []template.FuncMap {
                "Join":        strings.Join,
                "QueryEscape": url.QueryEscape,
                "DotEscape":   DotEscape,
-               "Iterate": func(arg interface{}) (items []uint64) {
-                       count := uint64(0)
-                       switch val := arg.(type) {
-                       case uint64:
-                               count = val
-                       case *uint64:
-                               count = *val
-                       case int64:
-                               if val < 0 {
-                                       val = 0
-                               }
-                               count = uint64(val)
-                       case *int64:
-                               if *val < 0 {
-                                       *val = 0
-                               }
-                               count = uint64(*val)
-                       case int:
-                               if val < 0 {
-                                       val = 0
-                               }
-                               count = uint64(val)
-                       case *int:
-                               if *val < 0 {
-                                       *val = 0
-                               }
-                               count = uint64(*val)
-                       case uint:
-                               count = uint64(val)
-                       case *uint:
-                               count = uint64(*val)
-                       case int32:
-                               if val < 0 {
-                                       val = 0
-                               }
-                               count = uint64(val)
-                       case *int32:
-                               if *val < 0 {
-                                       *val = 0
-                               }
-                               count = uint64(*val)
-                       case uint32:
-                               count = uint64(val)
-                       case *uint32:
-                               count = uint64(*val)
-                       case string:
-                               cnt, _ := strconv.ParseInt(val, 10, 64)
-                               if cnt < 0 {
-                                       cnt = 0
-                               }
-                               count = uint64(cnt)
-                       }
-                       if count <= 0 {
-                               return items
-                       }
-                       for i := uint64(0); i < count; i++ {
+               "Iterate": func(arg interface{}) (items []int64) {
+                       count := util.ToInt64(arg)
+                       for i := int64(0); i < count; i++ {
                                items = append(items, i)
                        }
                        return items
@@ -1067,10 +1012,8 @@ func mirrorRemoteAddress(ctx context.Context, m *repo_model.Repository, remoteNa
        return a
 }
 
-// JsPrettyNumber renders a number using english decimal separators, e.g. 1,200 and subsequent
-// JS will replace the number with locale-specific separators, based on the user's selected language
-func JsPrettyNumber(i interface{}) template.HTML {
-       num := util.NumberIntoInt64(i)
-
-       return template.HTML(`<span class="js-pretty-number" data-value="` + strconv.FormatInt(num, 10) + `">` + base.PrettyNumber(num) + `</span>`)
+// LocaleNumber renders a number with a Custom Element, browser will render it with a locale number
+func LocaleNumber(v interface{}) template.HTML {
+       num := util.ToInt64(v)
+       return template.HTML(fmt.Sprintf(`<gitea-locale-number data-number="%d">%d</gitea-locale-number>`, num, num))
 }
index 032a6c0872c9703dad2ce79820a67b8c587fdaf9..f41d27d8b7432efa0b9d54f4afe50c2100e8e5a7 100644 (file)
@@ -35,27 +35,3 @@ func SplitStringAtByteN(input string, n int) (left, right string) {
 
        return input[:end] + utf8Ellipsis, utf8Ellipsis + input[end:]
 }
-
-// SplitStringAtRuneN splits a string at rune n accounting for rune boundaries. (Combining characters are not accounted for.)
-func SplitStringAtRuneN(input string, n int) (left, right string) {
-       if !utf8.ValidString(input) {
-               if len(input) <= n || n-3 < 0 {
-                       return input, ""
-               }
-               return input[:n-3] + asciiEllipsis, asciiEllipsis + input[n-3:]
-       }
-
-       if utf8.RuneCountInString(input) <= n {
-               return input, ""
-       }
-
-       count := 0
-       end := 0
-       for count < n-1 {
-               _, size := utf8.DecodeRuneInString(input[end:])
-               end += size
-               count++
-       }
-
-       return input[:end] + utf8Ellipsis, utf8Ellipsis + input[end:]
-}
index 912bfb3d5fde0c22d2b95086e815badb5a2c72f4..05e2bc03019b2914d2418af98b721aa286879f94 100644 (file)
@@ -43,18 +43,4 @@ func TestSplitString(t *testing.T) {
                {"\xef\x03", 1, "\xef\x03", ""},
        }
        test(tc, SplitStringAtByteN)
-
-       tc = []*testCase{
-               {"abc123xyz", 0, "", utf8Ellipsis},
-               {"abc123xyz", 1, "", utf8Ellipsis},
-               {"abc123xyz", 4, "abc", utf8Ellipsis},
-               {"啊bc123xyz", 4, "啊bc", utf8Ellipsis},
-               {"啊bc123xyz", 6, "啊bc12", utf8Ellipsis},
-               {"啊bc", 3, "啊bc", ""},
-               {"啊bc", 4, "啊bc", ""},
-               {"abc\xef\x03\xfe", 3, "", asciiEllipsis},
-               {"abc\xef\x03\xfe", 4, "a", asciiEllipsis},
-               {"\xef\x03", 1, "\xef\x03", ""},
-       }
-       test(tc, SplitStringAtRuneN)
 }
index 9d3a8dcfac219372d6fc23908f0fca98c096d40d..9c7097ad34c6b8341e4d70207052ebe8c5aa9096 100644 (file)
@@ -7,8 +7,9 @@ import (
        "bytes"
        "crypto/rand"
        "errors"
+       "fmt"
        "math/big"
-       "regexp"
+       "os"
        "strconv"
        "strings"
 
@@ -200,40 +201,14 @@ func ToTitleCaseNoLower(s string) string {
        return titleCaserNoLower.String(s)
 }
 
-var (
-       whitespaceOnly    = regexp.MustCompile("(?m)^[ \t]+$")
-       leadingWhitespace = regexp.MustCompile("(?m)(^[ \t]*)(?:[^ \t\n])")
-)
-
-// Dedent removes common indentation of a multi-line string along with whitespace around it
-// Based on https://github.com/lithammer/dedent
-func Dedent(s string) string {
-       var margin string
-
-       s = whitespaceOnly.ReplaceAllString(s, "")
-       indents := leadingWhitespace.FindAllStringSubmatch(s, -1)
-
-       for i, indent := range indents {
-               if i == 0 {
-                       margin = indent[1]
-               } else if strings.HasPrefix(indent[1], margin) {
-                       continue
-               } else if strings.HasPrefix(margin, indent[1]) {
-                       margin = indent[1]
-               } else {
-                       margin = ""
-                       break
-               }
-       }
-
-       if margin != "" {
-               s = regexp.MustCompile("(?m)^"+margin).ReplaceAllString(s, "")
-       }
-       return strings.TrimSpace(s)
+func logError(msg string, args ...any) {
+       // TODO: the "util" package can not import the "modules/log" package, so we use the "fmt" package here temporarily.
+       // In the future, we should decouple the dependency between them.
+       _, _ = fmt.Fprintf(os.Stderr, msg, args...)
 }
 
-// NumberIntoInt64 transform a given int into int64.
-func NumberIntoInt64(number interface{}) int64 {
+// ToInt64 transform a given int into int64.
+func ToInt64(number interface{}) int64 {
        var value int64
        switch v := number.(type) {
        case int:
@@ -246,6 +221,23 @@ func NumberIntoInt64(number interface{}) int64 {
                value = int64(v)
        case int64:
                value = v
+       case uint:
+               value = int64(v)
+       case uint8:
+               value = int64(v)
+       case uint16:
+               value = int64(v)
+       case uint32:
+               value = int64(v)
+       case uint64:
+               value = int64(v)
+       case string:
+               var err error
+               if value, err = strconv.ParseInt(v, 10, 64); err != nil {
+                       logError("strconv.ParseInt failed for %q: %v", v, err)
+               }
+       default:
+               logError("unable to convert %q to int64", v)
        }
        return value
 }
index 34fe070d22d448fabdd94784415d41e3e966c428..8cceafa2f66d706c80e357d2b6450e498eba9b50 100644 (file)
@@ -224,10 +224,3 @@ func TestToTitleCase(t *testing.T) {
        assert.Equal(t, ToTitleCase(`foo bar baz`), `Foo Bar Baz`)
        assert.Equal(t, ToTitleCase(`FOO BAR BAZ`), `Foo Bar Baz`)
 }
-
-func TestDedent(t *testing.T) {
-       assert.Equal(t, Dedent(`
-               foo
-                       bar
-       `), "foo\n\tbar")
-}
index 5062109161ee1f240325e7091f2b213692569f32..73ae5ab6e407656b02b872cd35b93be88acfb270 100644 (file)
                <div class="ui compact tiny menu">
                        <a class="item{{if not .IsShowClosed}} active{{end}}" href="{{$.Link}}?state=open">
                                {{svg "octicon-project-symlink" 16 "gt-mr-3"}}
-                               {{JsPrettyNumber .OpenCount}}&nbsp;{{.locale.Tr "repo.issues.open_title"}}
+                               {{LocaleNumber .OpenCount}}&nbsp;{{.locale.Tr "repo.issues.open_title"}}
                        </a>
                        <a class="item{{if .IsShowClosed}} active{{end}}" href="{{$.Link}}?state=closed">
                                {{svg "octicon-check" 16 "gt-mr-3"}}
-                               {{JsPrettyNumber .ClosedCount}}&nbsp;{{.locale.Tr "repo.issues.closed_title"}}
+                               {{LocaleNumber .ClosedCount}}&nbsp;{{.locale.Tr "repo.issues.closed_title"}}
                        </a>
                </div>
 
@@ -46,9 +46,9 @@
                                                {{end}}
                                                <span class="issue-stats">
                                                        {{svg "octicon-issue-opened" 16 "gt-mr-3"}}
-                                                       {{JsPrettyNumber .NumOpenIssues}}&nbsp;{{$.locale.Tr "repo.issues.open_title"}}
+                                                       {{LocaleNumber .NumOpenIssues}}&nbsp;{{$.locale.Tr "repo.issues.open_title"}}
                                                        {{svg "octicon-check" 16 "gt-mr-3"}}
-                                                       {{JsPrettyNumber .NumClosedIssues}}&nbsp;{{$.locale.Tr "repo.issues.closed_title"}}
+                                                       {{LocaleNumber .NumClosedIssues}}&nbsp;{{$.locale.Tr "repo.issues.closed_title"}}
                                                </span>
                                        </div>
                                        {{if and $.CanWriteProjects (not $.Repository.IsArchived)}}
index 0a113e2484c952dfc49d05fff4ce55c33d869d43..0336d35c17872627c820eafb27f56b96779b98a1 100644 (file)
                                <div class="ui compact tiny menu">
                                        <a class="item{{if not .IsShowClosed}} active{{end}}" href="{{.RepoLink}}/milestones?state=open&q={{$.Keyword}}">
                                                {{svg "octicon-milestone" 16 "gt-mr-3"}}
-                                               {{JsPrettyNumber .OpenCount}}&nbsp;{{.locale.Tr "repo.issues.open_title"}}
+                                               {{LocaleNumber .OpenCount}}&nbsp;{{.locale.Tr "repo.issues.open_title"}}
                                        </a>
                                        <a class="item{{if .IsShowClosed}} active{{end}}" href="{{.RepoLink}}/milestones?state=closed&q={{$.Keyword}}">
                                                {{svg "octicon-check" 16 "gt-mr-3"}}
-                                               {{JsPrettyNumber .ClosedCount}}&nbsp;{{.locale.Tr "repo.issues.closed_title"}}
+                                               {{LocaleNumber .ClosedCount}}&nbsp;{{.locale.Tr "repo.issues.closed_title"}}
                                        </a>
                                </div>
                        </div>
@@ -84,9 +84,9 @@
                                                {{end}}
                                                <span class="issue-stats">
                                                        {{svg "octicon-issue-opened" 16 "gt-mr-3"}}
-                                                       {{JsPrettyNumber .NumOpenIssues}}&nbsp;{{$.locale.Tr "repo.issues.open_title"}}
+                                                       {{LocaleNumber .NumOpenIssues}}&nbsp;{{$.locale.Tr "repo.issues.open_title"}}
                                                        {{svg "octicon-check" 16 "gt-mr-3"}}
-                                                       {{JsPrettyNumber .NumClosedIssues}}&nbsp;{{$.locale.Tr "repo.issues.closed_title"}}
+                                                       {{LocaleNumber .NumClosedIssues}}&nbsp;{{$.locale.Tr "repo.issues.closed_title"}}
                                                        {{if .TotalTrackedTime}}{{svg "octicon-clock"}} {{.TotalTrackedTime|Sec2Time}}{{end}}
                                                        {{if .UpdatedUnix}}{{svg "octicon-clock"}} {{$.locale.Tr "repo.milestones.update_ago" (.TimeSinceUpdate|Sec2Time)}}{{end}}
                                                </span>
index e2c13fea18790e649bc59f56bcc69728724da827..6eb26b36c5d6d0088d962ca5def02218fdf189bb 100644 (file)
@@ -5,10 +5,10 @@
                {{else}}
                        {{svg "octicon-issue-opened" 16 "gt-mr-3"}}
                {{end}}
-               {{JsPrettyNumber .IssueStats.OpenCount}}&nbsp;{{.locale.Tr "repo.issues.open_title"}}
+               {{LocaleNumber .IssueStats.OpenCount}}&nbsp;{{.locale.Tr "repo.issues.open_title"}}
        </a>
        <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}}">
                {{svg "octicon-check" 16 "gt-mr-3"}}
-               {{JsPrettyNumber .IssueStats.ClosedCount}}&nbsp;{{.locale.Tr "repo.issues.closed_title"}}
+               {{LocaleNumber .IssueStats.ClosedCount}}&nbsp;{{.locale.Tr "repo.issues.closed_title"}}
        </a>
 </div>
index 2350a3af546b502ee42375a11fc50f77eb710607..227a2727055c79149ae76bf8d508e2d9d003ba25 100644 (file)
                <div class="ui compact tiny menu">
                        <a class="item{{if not .IsShowClosed}} active{{end}}" href="{{.RepoLink}}/projects?state=open">
                                {{svg "octicon-project" 16 "gt-mr-3"}}
-                               {{JsPrettyNumber .OpenCount}}&nbsp;{{.locale.Tr "repo.issues.open_title"}}
+                               {{LocaleNumber .OpenCount}}&nbsp;{{.locale.Tr "repo.issues.open_title"}}
                        </a>
                        <a class="item{{if .IsShowClosed}} active{{end}}" href="{{.RepoLink}}/projects?state=closed">
                                {{svg "octicon-check" 16 "gt-mr-3"}}
-                               {{JsPrettyNumber .ClosedCount}}&nbsp;{{.locale.Tr "repo.issues.closed_title"}}
+                               {{LocaleNumber .ClosedCount}}&nbsp;{{.locale.Tr "repo.issues.closed_title"}}
                        </a>
                </div>
 
@@ -48,9 +48,9 @@
                                                {{end}}
                                                <span class="issue-stats">
                                                        {{svg "octicon-issue-opened" 16 "gt-mr-3"}}
-                                                       {{JsPrettyNumber .NumOpenIssues}}&nbsp;{{$.locale.Tr "repo.issues.open_title"}}
+                                                       {{LocaleNumber .NumOpenIssues}}&nbsp;{{$.locale.Tr "repo.issues.open_title"}}
                                                        {{svg "octicon-check" 16 "gt-mr-3"}}
-                                                       {{JsPrettyNumber .NumClosedIssues}}&nbsp;{{$.locale.Tr "repo.issues.closed_title"}}
+                                                       {{LocaleNumber .NumClosedIssues}}&nbsp;{{$.locale.Tr "repo.issues.closed_title"}}
                                                </span>
                                        </div>
                                        {{if and $.CanWriteProjects (not $.Repository.IsArchived)}}
index 3dc3deb3954c77e59ba477f47117fb783e80417e..9bc87fa80b384d3bfb269537b2dcf0a3bdfb8aef 100644 (file)
                                                                                        <li>
                                                                                                <span class="ui text middle aligned right">
                                                                                                        <span class="ui text grey">{{.Size | FileSize}}</span>
-                                                                                                       <span data-tooltip-content="{{$.locale.Tr "repo.release.download_count" (.DownloadCount | PrettyNumber)}}">
+                                                                                                       <gitea-locale-number data-number-in-tooltip="{{dict "message" ($.locale.Tr "repo.release.download_count") "number" .DownloadCount | Json}}">
                                                                                                                {{svg "octicon-info"}}
-                                                                                                       </span>
+                                                                                                       </gitea-locale-number>
                                                                                                </span>
                                                                                                <a target="_blank" rel="noopener noreferrer" href="{{.DownloadURL}}">
                                                                                                        <strong>{{svg "octicon-package" 16 "gt-mr-2"}}{{.Name}}</strong>
index 589fe12cea7931616bc1fdf3ac43e6dcfc82f08b..ea5c70e7428596a22d250f799367f03666d10e62 100644 (file)
@@ -72,9 +72,9 @@
                                                        <input name="attachment-edit-{{.UUID}}" class="gt-mr-3 attachment_edit" required value="{{.Name}}">
                                                        <input name="attachment-del-{{.UUID}}" type="hidden" value="false">
                                                        <span class="ui text grey gt-mr-3">{{.Size | FileSize}}</span>
-                                                       <span data-tooltip-content="{{$.locale.Tr "repo.release.download_count" (.DownloadCount | PrettyNumber)}}">
+                                                       <gitea-locale-number data-number-in-tooltip="{{dict "message" ($.locale.Tr "repo.release.download_count") "number" .DownloadCount | Json}}">
                                                                {{svg "octicon-info"}}
-                                                       </span>
+                                                       </gitea-locale-number>
                                                </div>
                                        </div>
                                {{end}}
index 61d23915c3f255e99889cb7ae39028af3b500a21..fe4148d7447523356d81154047631df986125e5c 100644 (file)
@@ -4,7 +4,7 @@
                <div class="ui two horizontal center link list">
                        {{if and (.Permission.CanRead $.UnitTypeCode) (not .IsEmptyRepo)}}
                                <div class="item{{if .PageIsCommits}} active{{end}}">
-                                       <a href="{{.RepoLink}}/commits/{{.BranchNameSubURL}}">{{svg "octicon-history"}} <b>{{JsPrettyNumber .CommitsCount}}</b> {{.locale.TrN .CommitsCount "repo.commit" "repo.commits"}}</a>
+                                       <a href="{{.RepoLink}}/commits/{{.BranchNameSubURL}}">{{svg "octicon-history"}} <b>{{LocaleNumber .CommitsCount}}</b> {{.locale.TrN .CommitsCount "repo.commit" "repo.commits"}}</a>
                                </div>
                                <div class="item{{if .PageIsBranches}} active{{end}}">
                                        <a href="{{.RepoLink}}/branches">{{svg "octicon-git-branch"}} <b>{{.BranchesCount}}</b> {{.locale.TrN .BranchesCount "repo.branch" "repo.branches"}}</a>
index 766a6bf7d84606f9f496c7c5058cb406b611d339..88ab1f9b7371f9cbc02a5ca3ea9f22604830f1ca 100644 (file)
                                                <div class="ui compact tiny menu">
                                                        <a class="item{{if not .IsShowClosed}} active{{end}}" href="{{.Link}}?type={{$.ViewType}}&repos=[{{range $.RepoIDs}}{{.}}%2C{{end}}]&sort={{$.SortType}}&state=open&q={{$.Keyword}}">
                                                                {{svg "octicon-issue-opened" 16 "gt-mr-3"}}
-                                                               {{JsPrettyNumber .IssueStats.OpenCount}}&nbsp;{{.locale.Tr "repo.issues.open_title"}}
+                                                               {{LocaleNumber .IssueStats.OpenCount}}&nbsp;{{.locale.Tr "repo.issues.open_title"}}
                                                        </a>
                                                        <a class="item{{if .IsShowClosed}} active{{end}}" href="{{.Link}}?type={{$.ViewType}}&repos=[{{range $.RepoIDs}}{{.}}%2C{{end}}]&sort={{$.SortType}}&state=closed&q={{$.Keyword}}">
                                                                {{svg "octicon-issue-closed" 16 "gt-mr-3"}}
-                                                               {{JsPrettyNumber .IssueStats.ClosedCount}}&nbsp;{{.locale.Tr "repo.issues.closed_title"}}
+                                                               {{LocaleNumber .IssueStats.ClosedCount}}&nbsp;{{.locale.Tr "repo.issues.closed_title"}}
                                                        </a>
                                                </div>
                                        </div>
index c20ea9d041789f001472692c1b55b239bbba124b..1c379ac09d025345b30e25b645489e863a8ccfb7 100644 (file)
                                                <div class="ui compact tiny menu">
                                                        <a class="item{{if not .IsShowClosed}} active{{end}}" href="{{.Link}}?repos=[{{range $.RepoIDs}}{{.}}%2C{{end}}]&sort={{$.SortType}}&state=open&q={{$.Keyword}}">
                                                                {{svg "octicon-milestone" 16 "gt-mr-3"}}
-                                                               {{JsPrettyNumber .MilestoneStats.OpenCount}}&nbsp;{{.locale.Tr "repo.issues.open_title"}}
+                                                               {{LocaleNumber .MilestoneStats.OpenCount}}&nbsp;{{.locale.Tr "repo.issues.open_title"}}
                                                        </a>
                                                        <a class="item{{if .IsShowClosed}} active{{end}}" href="{{.Link}}?repos=[{{range $.RepoIDs}}{{.}}%2C{{end}}]&sort={{$.SortType}}&state=closed&q={{$.Keyword}}">
                                                                {{svg "octicon-check" 16 "gt-mr-3"}}
-                                                               {{JsPrettyNumber .MilestoneStats.ClosedCount}}&nbsp;{{.locale.Tr "repo.issues.closed_title"}}
+                                                               {{LocaleNumber .MilestoneStats.ClosedCount}}&nbsp;{{.locale.Tr "repo.issues.closed_title"}}
                                                        </a>
                                                </div>
                                        </div>
                                                                {{end}}
                                                                <span class="issue-stats">
                                                                        {{svg "octicon-issue-opened" 16 "gt-mr-3"}}
-                                                                       {{JsPrettyNumber .NumOpenIssues}}&nbsp;{{$.locale.Tr "repo.issues.open_title"}}
+                                                                       {{LocaleNumber .NumOpenIssues}}&nbsp;{{$.locale.Tr "repo.issues.open_title"}}
                                                                        {{svg "octicon-check" 16 "gt-mr-3"}}
-                                                                       {{JsPrettyNumber .NumClosedIssues}}&nbsp;{{$.locale.Tr "repo.issues.closed_title"}}
+                                                                       {{LocaleNumber .NumClosedIssues}}&nbsp;{{$.locale.Tr "repo.issues.closed_title"}}
                                                                        {{if .TotalTrackedTime}}
                                                                                {{svg "octicon-clock"}} {{.TotalTrackedTime|Sec2Time}}
                                                                        {{end}}
index 837e323376041fa498be52f61f84b1d0c61e8318..5590ba44d144851f215b78e55a4231e560a990bb 100644 (file)
@@ -1,20 +1,9 @@
-import {prettyNumber} from '../utils.js';
-
 const {lang} = document.documentElement;
 const dateFormatter = new Intl.DateTimeFormat(lang, {year: 'numeric', month: 'long', day: 'numeric'});
 const shortDateFormatter = new Intl.DateTimeFormat(lang, {year: 'numeric', month: 'short', day: 'numeric'});
 const dateTimeFormatter = new Intl.DateTimeFormat(lang, {year: 'numeric', month: 'short', day: 'numeric', hour: 'numeric', minute: 'numeric', second: 'numeric'});
 
 export function initFormattingReplacements() {
-  // replace english formatted numbers with locale-specific separators
-  for (const el of document.getElementsByClassName('js-pretty-number')) {
-    const num = Number(el.getAttribute('data-value'));
-    const formatted = prettyNumber(num, lang);
-    if (formatted && formatted !== el.textContent) {
-      el.textContent = formatted;
-    }
-  }
-
   // for each <time></time> tag, if it has the data-format attribute, format
   // the text according to the user's chosen locale and formatter.
   formatAllTimeElements();
index e72e55dc65e1742655366c4f5be8b21b229e91d5..8e1568390865c69e23b509613832943b06dbfddc 100644 (file)
@@ -54,13 +54,6 @@ export function parseIssueHref(href) {
   return {owner, repo, type, index};
 }
 
-// pretty-print a number using locale-specific separators, e.g. 1200 -> 1,200
-export function prettyNumber(num, locale = 'en-US') {
-  if (typeof num !== 'number') return '';
-  const {format} = new Intl.NumberFormat(locale);
-  return format(num);
-}
-
 // parse a URL, either relative '/path' or absolute 'https://localhost/path'
 export function parseUrl(str) {
   return new URL(str, str.startsWith('http') ? undefined : window.location.origin);
index 46fbb28de486ba51d66cbb454d0bc76b46a5302d..2f9e5fb47d5e1e3ba01737afd0e64416f4d9ef7c 100644 (file)
@@ -1,7 +1,7 @@
 import {expect, test} from 'vitest';
 import {
   basename, extname, isObject, stripTags, joinPaths, parseIssueHref,
-  prettyNumber, parseUrl, translateMonth, translateDay, blobToDataURI,
+  parseUrl, translateMonth, translateDay, blobToDataURI,
   toAbsoluteUrl,
 } from './utils.js';
 
@@ -84,17 +84,6 @@ test('parseIssueHref', () => {
   expect(parseIssueHref('')).toEqual({owner: undefined, repo: undefined, type: undefined, index: undefined});
 });
 
-test('prettyNumber', () => {
-  expect(prettyNumber()).toEqual('');
-  expect(prettyNumber(null)).toEqual('');
-  expect(prettyNumber(undefined)).toEqual('');
-  expect(prettyNumber('1200')).toEqual('');
-  expect(prettyNumber(12345678, 'en-US')).toEqual('12,345,678');
-  expect(prettyNumber(12345678, 'de-DE')).toEqual('12.345.678');
-  expect(prettyNumber(12345678, 'be-BE')).toEqual('12 345 678');
-  expect(prettyNumber(12345678, 'hi-IN')).toEqual('1,23,45,678');
-});
-
 test('parseUrl', () => {
   expect(parseUrl('').pathname).toEqual('/');
   expect(parseUrl('/path').pathname).toEqual('/path');
diff --git a/web_src/js/webcomponents/GiteaLocaleNumber.js b/web_src/js/webcomponents/GiteaLocaleNumber.js
new file mode 100644 (file)
index 0000000..613aa67
--- /dev/null
@@ -0,0 +1,20 @@
+// Convert a number to a locale string by data-number attribute.
+// Or add a tooltip by data-number-in-tooltip attribute. JSON: {message: "count: %s", number: 123}
+window.customElements.define('gitea-locale-number', class extends HTMLElement {
+  connectedCallback() {
+    // ideally, the number locale formatting and plural processing should be done by backend with translation strings.
+    // if we have complete backend locale support (eg: Golang "x/text" package), we can drop this component.
+    const number = this.getAttribute('data-number');
+    if (number) {
+      this.attachShadow({mode: 'open'});
+      this.shadowRoot.textContent = new Intl.NumberFormat().format(Number(number));
+    }
+    const numberInTooltip = this.getAttribute('data-number-in-tooltip');
+    if (numberInTooltip) {
+      // TODO: only 2 usages of this, we can replace it with Golang's "x/text/number" package in the future
+      const {message, number} = JSON.parse(numberInTooltip);
+      const tooltipContent = message.replace(/%[ds]/, new Intl.NumberFormat().format(Number(number)));
+      this.setAttribute('data-tooltip-content', tooltipContent);
+    }
+  }
+});
index c8736ac5c5fe0c045a3ac44028d89f783702ce50..fca736064c0c920039f87da69720e20200eb9913 100644 (file)
@@ -1,6 +1,4 @@
-import '@webcomponents/custom-elements'; // automatically adds custom elements for older browsers that don't support it
-
-// this is a Gitea's private HTML component, it converts an absolute or relative URL to an absolute URL with the current origin
+// Convert an absolute or relative URL to an absolute URL with the current origin
 window.customElements.define('gitea-origin-url', class extends HTMLElement {
   connectedCallback() {
     const urlStr = this.getAttribute('data-url');
index eabbc24ad1da13e7d56be36cbd79928b78fb4089..2b586a63d2562395ef403d86d082e183bb2a1f36 100644 (file)
@@ -15,5 +15,4 @@ https://developer.mozilla.org/en-US/docs/Web/Web_Components
 
 There are still some components that are not migrated to web components yet:
 
-* `<span class="js-pretty-number">`
 * `<time data-format>`
diff --git a/web_src/js/webcomponents/webcomponents.js b/web_src/js/webcomponents/webcomponents.js
new file mode 100644 (file)
index 0000000..5c4afb1
--- /dev/null
@@ -0,0 +1,3 @@
+import '@webcomponents/custom-elements'; // polyfill for some browsers like Pale Moon
+import './GiteaLocaleNumber.js';
+import './GiteaOriginUrl.js';
index cfe4ba1526aae9b75bf2e395ed7edbc13d11b41f..72ca96f97a8f4d8b8880901e4210806d39554f24 100644 (file)
@@ -60,7 +60,7 @@ export default {
       fileURLToPath(new URL('web_src/css/index.css', import.meta.url)),
     ],
     webcomponents: [
-      fileURLToPath(new URL('web_src/js/webcomponents/GiteaOriginUrl.js', import.meta.url)),
+      fileURLToPath(new URL('web_src/js/webcomponents/webcomponents.js', import.meta.url)),
     ],
     swagger: [
       fileURLToPath(new URL('web_src/js/standalone/swagger.js', import.meta.url)),