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:
		
						commit
						385bcca788
					
				
					 25 changed files with 237 additions and 32 deletions
				
			
		| 
						 | 
				
			
			@ -295,6 +295,7 @@ package "code.gitea.io/gitea/modules/translation"
 | 
			
		|||
	func (MockLocale).TrString
 | 
			
		||||
	func (MockLocale).Tr
 | 
			
		||||
	func (MockLocale).TrN
 | 
			
		||||
	func (MockLocale).TrSize
 | 
			
		||||
	func (MockLocale).PrettyNumber
 | 
			
		||||
 | 
			
		||||
package "code.gitea.io/gitea/modules/util/filebuffer"
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -16,7 +16,6 @@ import (
 | 
			
		|||
	"code.gitea.io/gitea/models/db"
 | 
			
		||||
	"code.gitea.io/gitea/models/unit"
 | 
			
		||||
	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/log"
 | 
			
		||||
	"code.gitea.io/gitea/modules/markup"
 | 
			
		||||
| 
						 | 
				
			
			@ -24,6 +23,7 @@ import (
 | 
			
		|||
	"code.gitea.io/gitea/modules/setting"
 | 
			
		||||
	api "code.gitea.io/gitea/modules/structs"
 | 
			
		||||
	"code.gitea.io/gitea/modules/timeutil"
 | 
			
		||||
	"code.gitea.io/gitea/modules/translation"
 | 
			
		||||
	"code.gitea.io/gitea/modules/util"
 | 
			
		||||
 | 
			
		||||
	"xorm.io/builder"
 | 
			
		||||
| 
						 | 
				
			
			@ -249,13 +249,17 @@ func (repo *Repository) SizeDetails() []SizeDetail {
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
// 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
 | 
			
		||||
	sizeDetails := repo.SizeDetails()
 | 
			
		||||
	for _, detail := range sizeDetails {
 | 
			
		||||
		str.WriteString(fmt.Sprintf("%s: %s, ", detail.Name, base.FileSize(detail.Size)))
 | 
			
		||||
	for i, detail := range sizeDetails {
 | 
			
		||||
		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 {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -63,7 +63,7 @@ func NewFuncMap() template.FuncMap {
 | 
			
		|||
 | 
			
		||||
		// -----------------------------------------------------------------
 | 
			
		||||
		// time / number / format
 | 
			
		||||
		"FileSize":      base.FileSize,
 | 
			
		||||
		"FileSize":      FileSizePanic,
 | 
			
		||||
		"CountFmt":      base.FormatNumberSI,
 | 
			
		||||
		"TimeSince":     timeutil.TimeSince,
 | 
			
		||||
		"TimeSinceUnix": timeutil.TimeSinceUnix,
 | 
			
		||||
| 
						 | 
				
			
			@ -249,3 +249,7 @@ func Eval(tokens ...any) (any, error) {
 | 
			
		|||
	n, err := eval.Expr(tokens...)
 | 
			
		||||
	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.")
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -31,6 +31,10 @@ func (l MockLocale) TrN(cnt any, key1, keyN string, args ...any) template.HTML {
 | 
			
		|||
	return template.HTML(key1)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (l MockLocale) TrSize(s int64) ReadableSize {
 | 
			
		||||
	return ReadableSize{fmt.Sprint(s), ""}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (l MockLocale) PrettyNumber(v any) string {
 | 
			
		||||
	return fmt.Sprint(v)
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -16,6 +16,7 @@ import (
 | 
			
		|||
	"code.gitea.io/gitea/modules/translation/i18n"
 | 
			
		||||
	"code.gitea.io/gitea/modules/util"
 | 
			
		||||
 | 
			
		||||
	"github.com/dustin/go-humanize"
 | 
			
		||||
	"golang.org/x/text/language"
 | 
			
		||||
	"golang.org/x/text/message"
 | 
			
		||||
	"golang.org/x/text/number"
 | 
			
		||||
| 
						 | 
				
			
			@ -33,6 +34,8 @@ type Locale interface {
 | 
			
		|||
	Tr(key string, args ...any) template.HTML
 | 
			
		||||
	TrN(cnt any, key1, keyN string, args ...any) template.HTML
 | 
			
		||||
 | 
			
		||||
	TrSize(size int64) ReadableSize
 | 
			
		||||
 | 
			
		||||
	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...)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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 {
 | 
			
		||||
	// 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 {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -3,6 +3,8 @@
 | 
			
		|||
 | 
			
		||||
package translation
 | 
			
		||||
 | 
			
		||||
// TODO: make this package friendly to testing
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"testing"
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -11,9 +13,25 @@ import (
 | 
			
		|||
	"github.com/stretchr/testify/assert"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func TestPrettyNumber(t *testing.T) {
 | 
			
		||||
	// TODO: make this package friendly to testing
 | 
			
		||||
func TestTrSize(t *testing.T) {
 | 
			
		||||
	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()
 | 
			
		||||
 | 
			
		||||
	allLangMap = make(map[string]*LangType)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -3417,6 +3417,15 @@ years = %d years
 | 
			
		|||
raw_seconds = seconds
 | 
			
		||||
raw_minutes = minutes
 | 
			
		||||
 | 
			
		||||
[munits.data]
 | 
			
		||||
b = B
 | 
			
		||||
kib = KiB
 | 
			
		||||
mib = MiB
 | 
			
		||||
gib = GiB
 | 
			
		||||
tib = TiB
 | 
			
		||||
pib = PiB
 | 
			
		||||
eib = EiB
 | 
			
		||||
 | 
			
		||||
[dropzone]
 | 
			
		||||
default_message = Drop files or click here to upload.
 | 
			
		||||
invalid_input_type = You cannot upload files of this type.
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -3417,6 +3417,15 @@ years=%d лет
 | 
			
		|||
raw_seconds=секунд
 | 
			
		||||
raw_minutes=минут
 | 
			
		||||
 | 
			
		||||
[munits.data]
 | 
			
		||||
b = Б
 | 
			
		||||
kib = КиБ
 | 
			
		||||
mib = МиБ
 | 
			
		||||
gib = ГиБ
 | 
			
		||||
tib = ТиБ
 | 
			
		||||
pib = ПиБ
 | 
			
		||||
eib = ЕиБ
 | 
			
		||||
 | 
			
		||||
[dropzone]
 | 
			
		||||
default_message=Перетащите файл или кликните сюда для загрузки.
 | 
			
		||||
invalid_input_type=Вы не можете загружать файлы этого типа.
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -2,8 +2,8 @@
 | 
			
		|||
	<div class="admin-setting-content">
 | 
			
		||||
		<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.total_size" (FileSize .TotalBlobSize)}},
 | 
			
		||||
			{{ctx.Locale.Tr "admin.packages.unreferenced_size" (FileSize .TotalUnreferencedBlobSize)}})
 | 
			
		||||
			{{ctx.Locale.Tr "admin.packages.total_size" (ctx.Locale.TrSize .TotalBlobSize)}},
 | 
			
		||||
			{{ctx.Locale.Tr "admin.packages.unreferenced_size" (ctx.Locale.TrSize .TotalUnreferencedBlobSize)}})
 | 
			
		||||
			<div class="ui right">
 | 
			
		||||
				<form method="post" action="/admin/packages/cleanup">
 | 
			
		||||
					{{.CsrfTokenHtml}}
 | 
			
		||||
| 
						 | 
				
			
			@ -70,7 +70,7 @@
 | 
			
		|||
								<a href="{{.Repository.Link}}">{{.Repository.Name}}</a>
 | 
			
		||||
							{{end}}
 | 
			
		||||
							</td>
 | 
			
		||||
							<td>{{FileSize .CalculateBlobSize}}</td>
 | 
			
		||||
							<td>{{ctx.Locale.TrSize .CalculateBlobSize}}</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>
 | 
			
		||||
						</tr>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -80,8 +80,8 @@
 | 
			
		|||
							<td>{{.NumStars}}</td>
 | 
			
		||||
							<td>{{.NumForks}}</td>
 | 
			
		||||
							<td>{{.NumIssues}}</td>
 | 
			
		||||
							<td>{{FileSize .GitSize}}</td>
 | 
			
		||||
							<td>{{FileSize .LFSSize}}</td>
 | 
			
		||||
							<td>{{ctx.Locale.TrSize .GitSize}}</td>
 | 
			
		||||
							<td>{{ctx.Locale.TrSize .LFSSize}}</td>
 | 
			
		||||
							<td>{{DateTime "short" .UpdatedUnix}}</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>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -43,7 +43,7 @@
 | 
			
		|||
				{{range .Release.Attachments}}
 | 
			
		||||
					<li>
 | 
			
		||||
						<a target="_blank" rel="noopener noreferrer" href="{{.DownloadURL}}">
 | 
			
		||||
							<strong>{{.Name}} ({{.Size | FileSize}})</strong>
 | 
			
		||||
							<strong>{{.Name}} ({{.Size | $.locale.TrSize}})</strong>
 | 
			
		||||
						</a>
 | 
			
		||||
					</li>
 | 
			
		||||
				{{end}}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -39,7 +39,7 @@
 | 
			
		|||
					<tr>
 | 
			
		||||
						<td><a href="{{$.PackageDescriptor.PackageWebLink}}/{{PathEscape .Digest}}">{{.Digest}}</a></td>
 | 
			
		||||
						<td>{{.Platform}}</td>
 | 
			
		||||
						<td>{{FileSize .Size}}</td>
 | 
			
		||||
						<td>{{ctx.Locale.TrSize .Size}}</td>
 | 
			
		||||
					</tr>
 | 
			
		||||
					{{end}}
 | 
			
		||||
				</tbody>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -21,7 +21,7 @@
 | 
			
		|||
					<td>{{.Package.Name}}</td>
 | 
			
		||||
					<td><a href="{{.VersionWebLink}}">{{.Version.Version}}</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>
 | 
			
		||||
				</tr>
 | 
			
		||||
			{{else}}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -70,7 +70,7 @@
 | 
			
		|||
					{{template "package/metadata/swift" .}}
 | 
			
		||||
					{{template "package/metadata/vagrant" .}}
 | 
			
		||||
					{{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}}
 | 
			
		||||
				</div>
 | 
			
		||||
				{{if not (eq .PackageDescriptor.Package.Type "container")}}
 | 
			
		||||
| 
						 | 
				
			
			@ -80,7 +80,7 @@
 | 
			
		|||
					{{range .PackageDescriptor.Files}}
 | 
			
		||||
						<div class="item">
 | 
			
		||||
							<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>
 | 
			
		||||
					{{end}}
 | 
			
		||||
					</div>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -30,7 +30,7 @@
 | 
			
		|||
									{{ctx.Locale.Tr "repo.diff.file_image_height"}}: <span class="text bounds-info-height"></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>
 | 
			
		||||
						</span>
 | 
			
		||||
						{{end}}
 | 
			
		||||
| 
						 | 
				
			
			@ -45,7 +45,7 @@
 | 
			
		|||
									{{ctx.Locale.Tr "repo.diff.file_image_height"}}: <span class="text bounds-info-height"></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>
 | 
			
		||||
						</span>
 | 
			
		||||
						{{end}}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -11,7 +11,7 @@
 | 
			
		|||
	{{end}}
 | 
			
		||||
	{{if .FileSize}}
 | 
			
		||||
		<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>
 | 
			
		||||
	{{end}}
 | 
			
		||||
	{{if .LFSLock}}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -19,7 +19,7 @@
 | 
			
		|||
				</a>
 | 
			
		||||
			</div>
 | 
			
		||||
			<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>
 | 
			
		||||
	{{end -}}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -81,7 +81,7 @@
 | 
			
		|||
											<strong>{{svg "octicon-package" 16 "tw-mr-1"}}{{.Name}}</strong>
 | 
			
		||||
										</a>
 | 
			
		||||
										<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)}}">
 | 
			
		||||
												{{svg "octicon-info"}}
 | 
			
		||||
											</span>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -64,7 +64,7 @@
 | 
			
		|||
						<div class="flex-text-inline tw-flex-1">
 | 
			
		||||
							<input name="attachment-edit-{{.UUID}}"  class="attachment_edit" required value="{{.Name}}">
 | 
			
		||||
							<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)}}">
 | 
			
		||||
								{{svg "octicon-info"}}
 | 
			
		||||
							</span>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -16,7 +16,7 @@
 | 
			
		|||
								{{ShortSha .Oid}}
 | 
			
		||||
							</a>
 | 
			
		||||
						</td>
 | 
			
		||||
						<td>{{FileSize .Size}}</td>
 | 
			
		||||
						<td>{{ctx.Locale.TrSize .Size}}</td>
 | 
			
		||||
						<td>{{TimeSince .CreatedUnix.AsTime ctx.Locale}}</td>
 | 
			
		||||
						<td class="right aligned">
 | 
			
		||||
							<a class="ui primary button" href="{{$.Link}}/find?oid={{.Oid}}&size={{.Size}}">{{ctx.Locale.Tr "repo.settings.lfs_findcommits"}}</a>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -14,7 +14,7 @@
 | 
			
		|||
				</div>
 | 
			
		||||
				<div class="inline field">
 | 
			
		||||
					<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 class="inline field">
 | 
			
		||||
					<label>{{ctx.Locale.Tr "repo.template"}}</label>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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)}}
 | 
			
		||||
				</a>
 | 
			
		||||
			{{end}}
 | 
			
		||||
			<span class="item not-mobile" {{if not (eq .Repository.Size 0)}}data-tooltip-content="{{.Repository.SizeDetailsString}}"{{end}}>
 | 
			
		||||
				{{$fileSizeFormatted := FileSize .Repository.Size}}{{/* the formatted string is always "{val} {unit}" */}}
 | 
			
		||||
				{{$fileSizeFields := StringUtils.Split $fileSizeFormatted " "}}
 | 
			
		||||
				{{svg "octicon-database"}} <b>{{ctx.Locale.PrettyNumber (index $fileSizeFields 0)}}</b> {{index $fileSizeFields 1}}
 | 
			
		||||
			<span class="item not-mobile" {{if not (eq .Repository.Size 0)}}data-tooltip-content="{{.Repository.SizeDetailsString ctx.Locale}}"{{end}}>
 | 
			
		||||
				{{$fileSizeFields :=  ctx.Locale.TrSize .Repository.Size}}
 | 
			
		||||
				{{svg "octicon-database"}} <b>{{$fileSizeFields.PrettyNumber}}</b> {{$fileSizeFields.TranslatedUnit}}
 | 
			
		||||
			</span>
 | 
			
		||||
		{{end}}
 | 
			
		||||
	</div>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -24,7 +24,7 @@
 | 
			
		|||
											<span class="icon">{{svg "octicon-repo"}}</span>
 | 
			
		||||
										{{end}}
 | 
			
		||||
										<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}}
 | 
			
		||||
											{{ctx.Locale.Tr "repo.forked_from"}}
 | 
			
		||||
											<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"}}
 | 
			
		||||
									{{end}}
 | 
			
		||||
									<a class="name" href="{{.Link}}">{{.OwnerName}}/{{.Name}}</a>
 | 
			
		||||
									<span>{{FileSize .Size}}</span>
 | 
			
		||||
									<span>{{ctx.Locale.TrSize .Size}}</span>
 | 
			
		||||
									{{if .IsFork}}
 | 
			
		||||
										{{ctx.Locale.Tr "repo.forked_from"}}
 | 
			
		||||
										<span><a href="{{.BaseRepo.Link}}">{{.BaseRepo.OwnerName}}/{{.BaseRepo.Name}}</a></span>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -218,6 +218,16 @@ func (s *TestSession) GetCookie(name string) *http.Cookie {
 | 
			
		|||
	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 {
 | 
			
		||||
	t.Helper()
 | 
			
		||||
	req := rw.Request
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										115
									
								
								tests/integration/size_translations_test.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										115
									
								
								tests/integration/size_translations_test.go
									
										
									
									
									
										Normal 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)
 | 
			
		||||
}
 | 
			
		||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue