Merge pull request 'Data size unit localization' (#2528) from 0ko/forgejo:sizelocalize into forgejo

Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/2528
Reviewed-by: oliverpool <oliverpool@noreply.codeberg.org>
Reviewed-by: Gusted <gusted@noreply.codeberg.org>
This commit is contained in:
Earl Warren 2024-04-01 19:31:26 +00:00
commit 385bcca788
25 changed files with 237 additions and 32 deletions

View file

@ -295,6 +295,7 @@ package "code.gitea.io/gitea/modules/translation"
func (MockLocale).TrString func (MockLocale).TrString
func (MockLocale).Tr func (MockLocale).Tr
func (MockLocale).TrN func (MockLocale).TrN
func (MockLocale).TrSize
func (MockLocale).PrettyNumber func (MockLocale).PrettyNumber
package "code.gitea.io/gitea/modules/util/filebuffer" package "code.gitea.io/gitea/modules/util/filebuffer"

View file

@ -16,7 +16,6 @@ import (
"code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/models/unit" "code.gitea.io/gitea/models/unit"
user_model "code.gitea.io/gitea/models/user" user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/base"
"code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/markup" "code.gitea.io/gitea/modules/markup"
@ -24,6 +23,7 @@ import (
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
api "code.gitea.io/gitea/modules/structs" api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/timeutil" "code.gitea.io/gitea/modules/timeutil"
"code.gitea.io/gitea/modules/translation"
"code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/util"
"xorm.io/builder" "xorm.io/builder"
@ -249,13 +249,17 @@ func (repo *Repository) SizeDetails() []SizeDetail {
} }
// SizeDetailsString returns a concatenation of all repository size details as a string // SizeDetailsString returns a concatenation of all repository size details as a string
func (repo *Repository) SizeDetailsString() string { func (repo *Repository) SizeDetailsString(locale translation.Locale) string {
var str strings.Builder var str strings.Builder
sizeDetails := repo.SizeDetails() sizeDetails := repo.SizeDetails()
for _, detail := range sizeDetails { for i, detail := range sizeDetails {
str.WriteString(fmt.Sprintf("%s: %s, ", detail.Name, base.FileSize(detail.Size))) if i > 0 {
// TODO: use semicolon if decimal point of user localization is a comma
str.WriteString(", ")
}
str.WriteString(fmt.Sprintf("%s: %s", detail.Name, locale.TrSize(detail.Size)))
} }
return strings.TrimSuffix(str.String(), ", ") return str.String()
} }
func (repo *Repository) LogString() string { func (repo *Repository) LogString() string {

View file

@ -63,7 +63,7 @@ func NewFuncMap() template.FuncMap {
// ----------------------------------------------------------------- // -----------------------------------------------------------------
// time / number / format // time / number / format
"FileSize": base.FileSize, "FileSize": FileSizePanic,
"CountFmt": base.FormatNumberSI, "CountFmt": base.FormatNumberSI,
"TimeSince": timeutil.TimeSince, "TimeSince": timeutil.TimeSince,
"TimeSinceUnix": timeutil.TimeSinceUnix, "TimeSinceUnix": timeutil.TimeSinceUnix,
@ -249,3 +249,7 @@ func Eval(tokens ...any) (any, error) {
n, err := eval.Expr(tokens...) n, err := eval.Expr(tokens...)
return n.Value, err return n.Value, err
} }
func FileSizePanic(s int64) string {
panic("Usage of FileSize in templates is deprecated in Forgejo. Locale.TrSize should be used instead.")
}

View file

@ -31,6 +31,10 @@ func (l MockLocale) TrN(cnt any, key1, keyN string, args ...any) template.HTML {
return template.HTML(key1) return template.HTML(key1)
} }
func (l MockLocale) TrSize(s int64) ReadableSize {
return ReadableSize{fmt.Sprint(s), ""}
}
func (l MockLocale) PrettyNumber(v any) string { func (l MockLocale) PrettyNumber(v any) string {
return fmt.Sprint(v) return fmt.Sprint(v)
} }

View file

@ -16,6 +16,7 @@ import (
"code.gitea.io/gitea/modules/translation/i18n" "code.gitea.io/gitea/modules/translation/i18n"
"code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/util"
"github.com/dustin/go-humanize"
"golang.org/x/text/language" "golang.org/x/text/language"
"golang.org/x/text/message" "golang.org/x/text/message"
"golang.org/x/text/number" "golang.org/x/text/number"
@ -33,6 +34,8 @@ type Locale interface {
Tr(key string, args ...any) template.HTML Tr(key string, args ...any) template.HTML
TrN(cnt any, key1, keyN string, args ...any) template.HTML TrN(cnt any, key1, keyN string, args ...any) template.HTML
TrSize(size int64) ReadableSize
PrettyNumber(v any) string PrettyNumber(v any) string
} }
@ -252,6 +255,35 @@ func (l *locale) TrN(cnt any, key1, keyN string, args ...any) template.HTML {
return l.Tr(keyN, args...) return l.Tr(keyN, args...)
} }
type ReadableSize struct {
PrettyNumber string
TranslatedUnit string
}
func (bs ReadableSize) String() string {
return bs.PrettyNumber + " " + bs.TranslatedUnit
}
// TrSize returns array containing pretty formatted size and localized output of FileSize
// output of humanize.IBytes has to be split in order to be localized
func (l *locale) TrSize(s int64) ReadableSize {
us := uint64(s)
if s < 0 {
us = uint64(-s)
}
untranslated := humanize.IBytes(us)
if s < 0 {
untranslated = "-" + untranslated
}
numberVal, unitVal, found := strings.Cut(untranslated, " ")
if !found {
log.Error("no space in go-humanized size of %d: %q", s, untranslated)
}
numberVal = l.PrettyNumber(numberVal)
unitVal = l.TrString("munits.data." + strings.ToLower(unitVal))
return ReadableSize{numberVal, unitVal}
}
func (l *locale) PrettyNumber(v any) string { func (l *locale) PrettyNumber(v any) string {
// TODO: this mechanism is not good enough, the complete solution is to switch the translation system to ICU message format // TODO: this mechanism is not good enough, the complete solution is to switch the translation system to ICU message format
if s, ok := v.(string); ok { if s, ok := v.(string); ok {

View file

@ -3,6 +3,8 @@
package translation package translation
// TODO: make this package friendly to testing
import ( import (
"testing" "testing"
@ -11,9 +13,25 @@ import (
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
func TestPrettyNumber(t *testing.T) { func TestTrSize(t *testing.T) {
// TODO: make this package friendly to testing l := NewLocale("")
size := int64(1)
assert.EqualValues(t, "1 munits.data.b", l.TrSize(size).String())
size *= 2048
assert.EqualValues(t, "2 munits.data.kib", l.TrSize(size).String())
size *= 2048
assert.EqualValues(t, "4 munits.data.mib", l.TrSize(size).String())
size *= 2048
assert.EqualValues(t, "8 munits.data.gib", l.TrSize(size).String())
size *= 2048
assert.EqualValues(t, "16 munits.data.tib", l.TrSize(size).String())
size *= 2048
assert.EqualValues(t, "32 munits.data.pib", l.TrSize(size).String())
size *= 128
assert.EqualValues(t, "4 munits.data.eib", l.TrSize(size).String())
}
func TestPrettyNumber(t *testing.T) {
i18n.ResetDefaultLocales() i18n.ResetDefaultLocales()
allLangMap = make(map[string]*LangType) allLangMap = make(map[string]*LangType)

View file

@ -3417,6 +3417,15 @@ years = %d years
raw_seconds = seconds raw_seconds = seconds
raw_minutes = minutes raw_minutes = minutes
[munits.data]
b = B
kib = KiB
mib = MiB
gib = GiB
tib = TiB
pib = PiB
eib = EiB
[dropzone] [dropzone]
default_message = Drop files or click here to upload. default_message = Drop files or click here to upload.
invalid_input_type = You cannot upload files of this type. invalid_input_type = You cannot upload files of this type.

View file

@ -3417,6 +3417,15 @@ years=%d лет
raw_seconds=секунд raw_seconds=секунд
raw_minutes=минут raw_minutes=минут
[munits.data]
b = Б
kib = КиБ
mib = МиБ
gib = ГиБ
tib = ТиБ
pib = ПиБ
eib = ЕиБ
[dropzone] [dropzone]
default_message=Перетащите файл или кликните сюда для загрузки. default_message=Перетащите файл или кликните сюда для загрузки.
invalid_input_type=Вы не можете загружать файлы этого типа. invalid_input_type=Вы не можете загружать файлы этого типа.

View file

@ -2,8 +2,8 @@
<div class="admin-setting-content"> <div class="admin-setting-content">
<h4 class="ui top attached header"> <h4 class="ui top attached header">
{{ctx.Locale.Tr "admin.packages.package_manage_panel"}} ({{ctx.Locale.Tr "admin.total" .TotalCount}}, {{ctx.Locale.Tr "admin.packages.package_manage_panel"}} ({{ctx.Locale.Tr "admin.total" .TotalCount}},
{{ctx.Locale.Tr "admin.packages.total_size" (FileSize .TotalBlobSize)}}, {{ctx.Locale.Tr "admin.packages.total_size" (ctx.Locale.TrSize .TotalBlobSize)}},
{{ctx.Locale.Tr "admin.packages.unreferenced_size" (FileSize .TotalUnreferencedBlobSize)}}) {{ctx.Locale.Tr "admin.packages.unreferenced_size" (ctx.Locale.TrSize .TotalUnreferencedBlobSize)}})
<div class="ui right"> <div class="ui right">
<form method="post" action="/admin/packages/cleanup"> <form method="post" action="/admin/packages/cleanup">
{{.CsrfTokenHtml}} {{.CsrfTokenHtml}}
@ -70,7 +70,7 @@
<a href="{{.Repository.Link}}">{{.Repository.Name}}</a> <a href="{{.Repository.Link}}">{{.Repository.Name}}</a>
{{end}} {{end}}
</td> </td>
<td>{{FileSize .CalculateBlobSize}}</td> <td>{{ctx.Locale.TrSize .CalculateBlobSize}}</td>
<td>{{DateTime "short" .Version.CreatedUnix}}</td> <td>{{DateTime "short" .Version.CreatedUnix}}</td>
<td><a class="delete-button" href="" data-url="{{$.Link}}/delete?page={{$.Page.Paginater.Current}}&sort={{$.SortType}}" data-id="{{.Version.ID}}" data-name="{{.Package.Name}}" data-data-version="{{.Version.Version}}">{{svg "octicon-trash"}}</a></td> <td><a class="delete-button" href="" data-url="{{$.Link}}/delete?page={{$.Page.Paginater.Current}}&sort={{$.SortType}}" data-id="{{.Version.ID}}" data-name="{{.Package.Name}}" data-data-version="{{.Version.Version}}">{{svg "octicon-trash"}}</a></td>
</tr> </tr>

View file

@ -80,8 +80,8 @@
<td>{{.NumStars}}</td> <td>{{.NumStars}}</td>
<td>{{.NumForks}}</td> <td>{{.NumForks}}</td>
<td>{{.NumIssues}}</td> <td>{{.NumIssues}}</td>
<td>{{FileSize .GitSize}}</td> <td>{{ctx.Locale.TrSize .GitSize}}</td>
<td>{{FileSize .LFSSize}}</td> <td>{{ctx.Locale.TrSize .LFSSize}}</td>
<td>{{DateTime "short" .UpdatedUnix}}</td> <td>{{DateTime "short" .UpdatedUnix}}</td>
<td>{{DateTime "short" .CreatedUnix}}</td> <td>{{DateTime "short" .CreatedUnix}}</td>
<td><a class="delete-button" href="" data-url="{{$.Link}}/delete?page={{$.Page.Paginater.Current}}&sort={{$.SortType}}" data-id="{{.ID}}" data-name="{{.Name}}">{{svg "octicon-trash"}}</a></td> <td><a class="delete-button" href="" data-url="{{$.Link}}/delete?page={{$.Page.Paginater.Current}}&sort={{$.SortType}}" data-id="{{.ID}}" data-name="{{.Name}}">{{svg "octicon-trash"}}</a></td>

View file

@ -43,7 +43,7 @@
{{range .Release.Attachments}} {{range .Release.Attachments}}
<li> <li>
<a target="_blank" rel="noopener noreferrer" href="{{.DownloadURL}}"> <a target="_blank" rel="noopener noreferrer" href="{{.DownloadURL}}">
<strong>{{.Name}} ({{.Size | FileSize}})</strong> <strong>{{.Name}} ({{.Size | $.locale.TrSize}})</strong>
</a> </a>
</li> </li>
{{end}} {{end}}

View file

@ -39,7 +39,7 @@
<tr> <tr>
<td><a href="{{$.PackageDescriptor.PackageWebLink}}/{{PathEscape .Digest}}">{{.Digest}}</a></td> <td><a href="{{$.PackageDescriptor.PackageWebLink}}/{{PathEscape .Digest}}">{{.Digest}}</a></td>
<td>{{.Platform}}</td> <td>{{.Platform}}</td>
<td>{{FileSize .Size}}</td> <td>{{ctx.Locale.TrSize .Size}}</td>
</tr> </tr>
{{end}} {{end}}
</tbody> </tbody>

View file

@ -21,7 +21,7 @@
<td>{{.Package.Name}}</td> <td>{{.Package.Name}}</td>
<td><a href="{{.VersionWebLink}}">{{.Version.Version}}</a></td> <td><a href="{{.VersionWebLink}}">{{.Version.Version}}</a></td>
<td><a href="{{.Creator.HomeLink}}">{{.Creator.Name}}</a></td> <td><a href="{{.Creator.HomeLink}}">{{.Creator.Name}}</a></td>
<td>{{FileSize .CalculateBlobSize}}</td> <td>{{ctx.Locale.TrSize .CalculateBlobSize}}</td>
<td>{{DateTime "short" .Version.CreatedUnix}}</td> <td>{{DateTime "short" .Version.CreatedUnix}}</td>
</tr> </tr>
{{else}} {{else}}

View file

@ -70,7 +70,7 @@
{{template "package/metadata/swift" .}} {{template "package/metadata/swift" .}}
{{template "package/metadata/vagrant" .}} {{template "package/metadata/vagrant" .}}
{{if not (and (eq .PackageDescriptor.Package.Type "container") .PackageDescriptor.Metadata.Manifests)}} {{if not (and (eq .PackageDescriptor.Package.Type "container") .PackageDescriptor.Metadata.Manifests)}}
<div class="item">{{svg "octicon-database" 16 "tw-mr-2"}} {{FileSize .PackageDescriptor.CalculateBlobSize}}</div> <div class="item">{{svg "octicon-database" 16 "tw-mr-2"}} {{ctx.Locale.TrSize .PackageDescriptor.CalculateBlobSize}}</div>
{{end}} {{end}}
</div> </div>
{{if not (eq .PackageDescriptor.Package.Type "container")}} {{if not (eq .PackageDescriptor.Package.Type "container")}}
@ -80,7 +80,7 @@
{{range .PackageDescriptor.Files}} {{range .PackageDescriptor.Files}}
<div class="item"> <div class="item">
<a href="{{$.Link}}/files/{{.File.ID}}">{{.File.Name}}</a> <a href="{{$.Link}}/files/{{.File.ID}}">{{.File.Name}}</a>
<span class="text small file-size">{{FileSize .Blob.Size}}</span> <span class="text small file-size">{{ctx.Locale.TrSize .Blob.Size}}</span>
</div> </div>
{{end}} {{end}}
</div> </div>

View file

@ -30,7 +30,7 @@
{{ctx.Locale.Tr "repo.diff.file_image_height"}}: <span class="text bounds-info-height"></span> {{ctx.Locale.Tr "repo.diff.file_image_height"}}: <span class="text bounds-info-height"></span>
&nbsp;|&nbsp; &nbsp;|&nbsp;
</span> </span>
{{ctx.Locale.Tr "repo.diff.file_byte_size"}}: <span class="text">{{FileSize .blobBase.Size}}</span> {{ctx.Locale.Tr "repo.diff.file_byte_size"}}: <span class="text">{{ctx.Locale.TrSize .blobBase.Size}}</span>
</p> </p>
</span> </span>
{{end}} {{end}}
@ -45,7 +45,7 @@
{{ctx.Locale.Tr "repo.diff.file_image_height"}}: <span class="text bounds-info-height"></span> {{ctx.Locale.Tr "repo.diff.file_image_height"}}: <span class="text bounds-info-height"></span>
&nbsp;|&nbsp; &nbsp;|&nbsp;
</span> </span>
{{ctx.Locale.Tr "repo.diff.file_byte_size"}}: <span class="text">{{FileSize .blobHead.Size}}</span> {{ctx.Locale.Tr "repo.diff.file_byte_size"}}: <span class="text">{{ctx.Locale.TrSize .blobHead.Size}}</span>
</p> </p>
</span> </span>
{{end}} {{end}}

View file

@ -11,7 +11,7 @@
{{end}} {{end}}
{{if .FileSize}} {{if .FileSize}}
<div class="file-info-entry"> <div class="file-info-entry">
{{FileSize .FileSize}}{{if .IsLFSFile}} ({{ctx.Locale.Tr "repo.stored_lfs"}}){{end}} {{ctx.Locale.TrSize .FileSize}}{{if .IsLFSFile}} ({{ctx.Locale.Tr "repo.stored_lfs"}}){{end}}
</div> </div>
{{end}} {{end}}
{{if .LFSLock}} {{if .LFSLock}}

View file

@ -19,7 +19,7 @@
</a> </a>
</div> </div>
<div class="tw-p-2 tw-flex tw-items-center"> <div class="tw-p-2 tw-flex tw-items-center">
<span class="ui text grey">{{.Size | FileSize}}</span> <span class="ui text grey">{{.Size | ctx.Locale.TrSize}}</span>
</div> </div>
</div> </div>
{{end -}} {{end -}}

View file

@ -81,7 +81,7 @@
<strong>{{svg "octicon-package" 16 "tw-mr-1"}}{{.Name}}</strong> <strong>{{svg "octicon-package" 16 "tw-mr-1"}}{{.Name}}</strong>
</a> </a>
<div> <div>
<span class="text grey">{{.Size | FileSize}}</span> <span class="text grey">{{.Size | ctx.Locale.TrSize}}</span>
<span data-tooltip-content="{{ctx.Locale.Tr "repo.release.download_count" (ctx.Locale.PrettyNumber .DownloadCount)}}"> <span data-tooltip-content="{{ctx.Locale.Tr "repo.release.download_count" (ctx.Locale.PrettyNumber .DownloadCount)}}">
{{svg "octicon-info"}} {{svg "octicon-info"}}
</span> </span>

View file

@ -64,7 +64,7 @@
<div class="flex-text-inline tw-flex-1"> <div class="flex-text-inline tw-flex-1">
<input name="attachment-edit-{{.UUID}}" class="attachment_edit" required value="{{.Name}}"> <input name="attachment-edit-{{.UUID}}" class="attachment_edit" required value="{{.Name}}">
<input name="attachment-del-{{.UUID}}" type="hidden" value="false"> <input name="attachment-del-{{.UUID}}" type="hidden" value="false">
<span class="ui text grey tw-whitespace-nowrap">{{.Size | FileSize}}</span> <span class="ui text grey tw-whitespace-nowrap">{{.Size | ctx.Locale.TrSize}}</span>
<span data-tooltip-content="{{ctx.Locale.Tr "repo.release.download_count" (ctx.Locale.PrettyNumber .DownloadCount)}}"> <span data-tooltip-content="{{ctx.Locale.Tr "repo.release.download_count" (ctx.Locale.PrettyNumber .DownloadCount)}}">
{{svg "octicon-info"}} {{svg "octicon-info"}}
</span> </span>

View file

@ -16,7 +16,7 @@
{{ShortSha .Oid}} {{ShortSha .Oid}}
</a> </a>
</td> </td>
<td>{{FileSize .Size}}</td> <td>{{ctx.Locale.TrSize .Size}}</td>
<td>{{TimeSince .CreatedUnix.AsTime ctx.Locale}}</td> <td>{{TimeSince .CreatedUnix.AsTime ctx.Locale}}</td>
<td class="right aligned"> <td class="right aligned">
<a class="ui primary button" href="{{$.Link}}/find?oid={{.Oid}}&size={{.Size}}">{{ctx.Locale.Tr "repo.settings.lfs_findcommits"}}</a> <a class="ui primary button" href="{{$.Link}}/find?oid={{.Oid}}&size={{.Size}}">{{ctx.Locale.Tr "repo.settings.lfs_findcommits"}}</a>

View file

@ -14,7 +14,7 @@
</div> </div>
<div class="inline field"> <div class="inline field">
<label>{{ctx.Locale.Tr "repo.repo_size"}}</label> <label>{{ctx.Locale.Tr "repo.repo_size"}}</label>
<span {{if not (eq .Repository.Size 0)}} data-tooltip-content="{{.Repository.SizeDetailsString}}"{{end}}>{{FileSize .Repository.Size}}</span> <span {{if not (eq .Repository.Size 0)}} data-tooltip-content="{{.Repository.SizeDetailsString ctx.Locale}}"{{end}}>{{ctx.Locale.TrSize .Repository.Size}}</span>
</div> </div>
<div class="inline field"> <div class="inline field">
<label>{{ctx.Locale.Tr "repo.template"}}</label> <label>{{ctx.Locale.Tr "repo.template"}}</label>

View file

@ -13,10 +13,9 @@
{{svg "octicon-tag"}} {{ctx.Locale.TrN .NumTags "repo.n_tag_one" "repo.n_tag_few" (printf "<b>%d</b>" .NumTags | SafeHTML)}} {{svg "octicon-tag"}} {{ctx.Locale.TrN .NumTags "repo.n_tag_one" "repo.n_tag_few" (printf "<b>%d</b>" .NumTags | SafeHTML)}}
</a> </a>
{{end}} {{end}}
<span class="item not-mobile" {{if not (eq .Repository.Size 0)}}data-tooltip-content="{{.Repository.SizeDetailsString}}"{{end}}> <span class="item not-mobile" {{if not (eq .Repository.Size 0)}}data-tooltip-content="{{.Repository.SizeDetailsString ctx.Locale}}"{{end}}>
{{$fileSizeFormatted := FileSize .Repository.Size}}{{/* the formatted string is always "{val} {unit}" */}} {{$fileSizeFields := ctx.Locale.TrSize .Repository.Size}}
{{$fileSizeFields := StringUtils.Split $fileSizeFormatted " "}} {{svg "octicon-database"}} <b>{{$fileSizeFields.PrettyNumber}}</b> {{$fileSizeFields.TranslatedUnit}}
{{svg "octicon-database"}} <b>{{ctx.Locale.PrettyNumber (index $fileSizeFields 0)}}</b> {{index $fileSizeFields 1}}
</span> </span>
{{end}} {{end}}
</div> </div>

View file

@ -24,7 +24,7 @@
<span class="icon">{{svg "octicon-repo"}}</span> <span class="icon">{{svg "octicon-repo"}}</span>
{{end}} {{end}}
<a class="muted name" href="{{$repo.Link}}">{{$repo.OwnerName}}/{{$repo.Name}}</a> <a class="muted name" href="{{$repo.Link}}">{{$repo.OwnerName}}/{{$repo.Name}}</a>
<span class="text light-3" {{if not (eq $repo.Size 0)}} data-tooltip-content="{{$repo.SizeDetailsString}}"{{end}}>{{FileSize $repo.Size}}</span> <span class="text light-3" {{if not (eq $repo.Size 0)}} data-tooltip-content="{{$repo.SizeDetailsString ctx.Locale}}"{{end}}>{{ctx.Locale.TrSize $repo.Size}}</span>
{{if $repo.IsFork}} {{if $repo.IsFork}}
{{ctx.Locale.Tr "repo.forked_from"}} {{ctx.Locale.Tr "repo.forked_from"}}
<span><a href="{{$repo.BaseRepo.Link}}">{{$repo.BaseRepo.OwnerName}}/{{$repo.BaseRepo.Name}}</a></span> <span><a href="{{$repo.BaseRepo.Link}}">{{$repo.BaseRepo.OwnerName}}/{{$repo.BaseRepo.Name}}</a></span>
@ -97,7 +97,7 @@
{{svg "octicon-repo" 16 "tw-mr-1 iconFloat"}} {{svg "octicon-repo" 16 "tw-mr-1 iconFloat"}}
{{end}} {{end}}
<a class="name" href="{{.Link}}">{{.OwnerName}}/{{.Name}}</a> <a class="name" href="{{.Link}}">{{.OwnerName}}/{{.Name}}</a>
<span>{{FileSize .Size}}</span> <span>{{ctx.Locale.TrSize .Size}}</span>
{{if .IsFork}} {{if .IsFork}}
{{ctx.Locale.Tr "repo.forked_from"}} {{ctx.Locale.Tr "repo.forked_from"}}
<span><a href="{{.BaseRepo.Link}}">{{.BaseRepo.OwnerName}}/{{.BaseRepo.Name}}</a></span> <span><a href="{{.BaseRepo.Link}}">{{.BaseRepo.OwnerName}}/{{.BaseRepo.Name}}</a></span>

View file

@ -218,6 +218,16 @@ func (s *TestSession) GetCookie(name string) *http.Cookie {
return nil return nil
} }
func (s *TestSession) SetCookie(cookie *http.Cookie) *http.Cookie {
baseURL, err := url.Parse(setting.AppURL)
if err != nil {
return nil
}
s.jar.SetCookies(baseURL, []*http.Cookie{cookie})
return nil
}
func (s *TestSession) MakeRequest(t testing.TB, rw *RequestWrapper, expectedStatus int) *httptest.ResponseRecorder { func (s *TestSession) MakeRequest(t testing.TB, rw *RequestWrapper, expectedStatus int) *httptest.ResponseRecorder {
t.Helper() t.Helper()
req := rw.Request req := rw.Request

View file

@ -0,0 +1,115 @@
// Copyright 2024 The Forgejo Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package integration
import (
"net/http"
"net/url"
"path"
"regexp"
"strings"
"testing"
"code.gitea.io/gitea/models/unittest"
user_model "code.gitea.io/gitea/models/user"
files_service "code.gitea.io/gitea/services/repository/files"
"github.com/PuerkitoBio/goquery"
"github.com/stretchr/testify/assert"
)
func TestDataSizeTranslation(t *testing.T) {
onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) {
testUser := "user2"
testRepoName := "data_size_test"
noDigits := regexp.MustCompile("[0-9]+")
longString100 := `testRepoMigrate(t, session, "https://code.forgejo.org/forgejo/test_repo.git", testRepoName, struct)` + "\n"
// Login user
user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{Name: testUser})
session := loginUser(t, testUser)
// Create test repo
testRepo, _, f := CreateDeclarativeRepo(t, user2, testRepoName, nil, nil,
[]*files_service.ChangeRepoFile{
{
Operation: "create",
TreePath: "137byteFile.txt",
ContentReader: strings.NewReader(longString100 + strings.Repeat("1", 36) + "\n"),
},
{
Operation: "create",
TreePath: "1.5kibFile.txt",
ContentReader: strings.NewReader(strings.Repeat(longString100, 15) + strings.Repeat("1", 35) + "\n"),
},
{
Operation: "create",
TreePath: "1.25mibFile.txt",
ContentReader: strings.NewReader(strings.Repeat(longString100, 13107) + strings.Repeat("1", 19) + "\n"),
},
})
defer f()
// Change language from English to catch regressions that make translated sizes fall back to
// not translated, like to raw output of FileSize() or humanize.IBytes()
lang := session.GetCookie("lang")
lang.Value = "ru-RU"
session.SetCookie(lang)
// Go to /user/settings/repos
req := NewRequest(t, "GET", "user/settings/repos")
resp := session.MakeRequest(t, req, http.StatusOK)
// Check if repo size is translated
repos := NewHTMLParser(t, resp.Body).Find(".user-setting-content .list .item .content")
assert.True(t, repos.Length() > 0)
repos.Each(func(i int, repo *goquery.Selection) {
repoName := repo.Find("a.name").Text()
if repoName == path.Join(testUser, testRepo.Name) {
repoSize := repo.Find("span").Text()
repoSize = noDigits.ReplaceAllString(repoSize, "")
assert.Equal(t, " КиБ", repoSize)
}
})
// Go to /user2/repo1
req = NewRequest(t, "GET", path.Join(testUser, testRepoName))
resp = session.MakeRequest(t, req, http.StatusOK)
// Check if repo size in repo summary is translated
repo := NewHTMLParser(t, resp.Body).Find(".repository-summary span")
repoSize := strings.TrimSpace(repo.Text())
repoSize = noDigits.ReplaceAllString(repoSize, "")
assert.Equal(t, " КиБ", repoSize)
// Check if repo sizes in the tooltip are translated
fullSize, exists := repo.Attr("data-tooltip-content")
assert.True(t, exists)
fullSize = noDigits.ReplaceAllString(fullSize, "")
assert.Equal(t, "git: КиБ, lfs: Б", fullSize)
// Check if file sizes are correclty translated
testFileSizeTranslated(t, session, path.Join(testUser, testRepoName, "src/branch/main/137byteFile.txt"), "137 Б")
testFileSizeTranslated(t, session, path.Join(testUser, testRepoName, "src/branch/main/1.5kibFile.txt"), "1,5 КиБ")
testFileSizeTranslated(t, session, path.Join(testUser, testRepoName, "src/branch/main/1.25mibFile.txt"), "1,3 МиБ")
})
}
func testFileSizeTranslated(t *testing.T, session *TestSession, filePath, correctSize string) {
// Go to specified file page
req := NewRequest(t, "GET", filePath)
resp := session.MakeRequest(t, req, http.StatusOK)
// Check if file size is translated
sizeCorrent := false
fileInfo := NewHTMLParser(t, resp.Body).Find(".file-info .file-info-entry")
fileInfo.Each(func(i int, info *goquery.Selection) {
infoText := strings.TrimSpace(info.Text())
if infoText == correctSize {
sizeCorrent = true
}
})
assert.True(t, sizeCorrent)
}