mirror of
				https://github.com/go-gitea/gitea.git
				synced 2025-10-26 12:27:06 +00:00 
			
		
		
		
	Remove legacy unmaintained packages, refactor to support change default locale (#19308)
Remove two unmaintained vendor packages `i18n` and `paginater`. Changes: * Rewrite `i18n` package with a more clear fallback mechanism. Fix an unstable `Tr` behavior, add more tests. * Refactor the legacy `Paginater` to `Paginator`, test cases are kept unchanged. Trivial enhancement (no breaking for end users): * Use the first locale in LANGS setting option as the default, add a log to prevent from surprising users.
This commit is contained in:
		| @@ -2117,6 +2117,7 @@ PATH = | ||||
| ;[i18n] | ||||
| ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; | ||||
| ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; | ||||
| ;; The first locale will be used as the default if user browser's language doesn't match any locale in the list. | ||||
| ;LANGS = en-US,zh-CN,zh-HK,zh-TW,de-DE,fr-FR,nl-NL,lv-LV,ru-RU,uk-UA,ja-JP,es-ES,pt-BR,pt-PT,pl-PL,bg-BG,it-IT,fi-FI,tr-TR,cs-CZ,sr-SP,sv-SE,ko-KR,el-GR,fa-IR,hu-HU,id-ID,ml-IN | ||||
| ;NAMES = English,简体中文,繁體中文(香港),繁體中文(台灣),Deutsch,français,Nederlands,latviešu,русский,Українська,日本語,español,português do Brasil,Português de Portugal,polski,български,italiano,suomi,Türkçe,čeština,српски,svenska,한국어,ελληνικά,فارسی,magyar nyelv,bahasa Indonesia,മലയാളം | ||||
|  | ||||
|   | ||||
| @@ -997,7 +997,8 @@ Default templates for project boards: | ||||
|  | ||||
| ## i18n (`i18n`) | ||||
|  | ||||
| - `LANGS`: **en-US,zh-CN,zh-HK,zh-TW,de-DE,fr-FR,nl-NL,lv-LV,ru-RU,ja-JP,es-ES,pt-BR,pt-PT,pl-PL,bg-BG,it-IT,fi-FI,tr-TR,cs-CZ,sr-SP,sv-SE,ko-KR,el-GR,fa-IR,hu-HU,id-ID,ml-IN**: List of locales shown in language selector | ||||
| - `LANGS`: **en-US,zh-CN,zh-HK,zh-TW,de-DE,fr-FR,nl-NL,lv-LV,ru-RU,ja-JP,es-ES,pt-BR,pt-PT,pl-PL,bg-BG,it-IT,fi-FI,tr-TR,cs-CZ,sr-SP,sv-SE,ko-KR,el-GR,fa-IR,hu-HU,id-ID,ml-IN**: | ||||
|      List of locales shown in language selector. The first locale will be used as the default if user browser's language doesn't match any locale in the list. | ||||
| - `NAMES`: **English,简体中文,繁體中文(香港),繁體中文(台灣),Deutsch,français,Nederlands,latviešu,русский,日本語,español,português do Brasil,Português de Portugal,polski,български,italiano,suomi,Türkçe,čeština,српски,svenska,한국어,ελληνικά,فارسی,magyar nyelv,bahasa Indonesia,മലയാളം**: Visible names corresponding to the locales | ||||
|  | ||||
| ## U2F (`U2F`) **DEPRECATED** | ||||
|   | ||||
| @@ -299,6 +299,8 @@ LANGS = en-US,foo-BAR | ||||
| NAMES = English,FooBar | ||||
| ``` | ||||
|  | ||||
| The first locale will be used as the default if user browser's language doesn't match any locale in the list. | ||||
|  | ||||
| Locales may change between versions, so keeping track of your customized locales is highly encouraged. | ||||
|  | ||||
| ### Readmes | ||||
|   | ||||
							
								
								
									
										2
									
								
								go.mod
									
									
									
									
									
								
							
							
						
						
									
										2
									
								
								go.mod
									
									
									
									
									
								
							| @@ -78,8 +78,6 @@ require ( | ||||
| 	github.com/stretchr/testify v1.7.0 | ||||
| 	github.com/syndtr/goleveldb v1.0.0 | ||||
| 	github.com/tstranex/u2f v1.0.0 | ||||
| 	github.com/unknwon/i18n v0.0.0-20210904045753-ff3a8617e361 | ||||
| 	github.com/unknwon/paginater v0.0.0-20200328080006-042474bd0eae | ||||
| 	github.com/unrolled/render v1.4.1 | ||||
| 	github.com/urfave/cli v1.22.5 | ||||
| 	github.com/xanzy/go-gitlab v0.58.0 | ||||
|   | ||||
							
								
								
									
										5
									
								
								go.sum
									
									
									
									
									
								
							
							
						
						
									
										5
									
								
								go.sum
									
									
									
									
									
								
							| @@ -1503,10 +1503,6 @@ github.com/ulikunitz/xz v0.5.10/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0o | ||||
| github.com/unknwon/com v0.0.0-20190804042917-757f69c95f3e/go.mod h1:tOOxU81rwgoCLoOVVPHb6T/wt8HZygqH5id+GNnlCXM= | ||||
| github.com/unknwon/com v1.0.1 h1:3d1LTxD+Lnf3soQiD4Cp/0BRB+Rsa/+RTvz8GMMzIXs= | ||||
| github.com/unknwon/com v1.0.1/go.mod h1:tOOxU81rwgoCLoOVVPHb6T/wt8HZygqH5id+GNnlCXM= | ||||
| github.com/unknwon/i18n v0.0.0-20210904045753-ff3a8617e361 h1:4Ij5sX4JEzCCY/CCl8trJHey1tPsIDomYTZf145GKk0= | ||||
| github.com/unknwon/i18n v0.0.0-20210904045753-ff3a8617e361/go.mod h1:+5rDk6sDGpl3azws3O+f+GpFSyN9GVr0K8cvQLQM2ZQ= | ||||
| github.com/unknwon/paginater v0.0.0-20200328080006-042474bd0eae h1:ihaXiJkaca54IaCSnEXtE/uSZOmPxKZhDfVLrzZLFDs= | ||||
| github.com/unknwon/paginater v0.0.0-20200328080006-042474bd0eae/go.mod h1:1fdkY6xxl6ExVs2QFv7R0F5IRZHKA8RahhB9fMC9RvM= | ||||
| github.com/unrolled/render v1.4.1 h1:VdpMc2YkAOWzbmC/P2yoHhRDXgsaCQHcTJ1KK6SNCA4= | ||||
| github.com/unrolled/render v1.4.1/go.mod h1:cK4RSTTVdND5j9EYEc0LAMOvdG11JeiKjyjfyZRvV2w= | ||||
| github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= | ||||
| @@ -2272,7 +2268,6 @@ gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df h1:n7WqCuqOuCbNr617RXOY0AW | ||||
| gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df/go.mod h1:LRQQ+SO6ZHR7tOkpBDuZnXENFzX8qRjMDMyPD6BRkCw= | ||||
| gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s= | ||||
| gopkg.in/ini.v1 v1.44.2/go.mod h1:M3Cogqpuv0QCi3ExAY5V4uOt4qb/R3xZubo9m8lK5wg= | ||||
| gopkg.in/ini.v1 v1.46.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= | ||||
| gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= | ||||
| gopkg.in/ini.v1 v1.57.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= | ||||
| gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= | ||||
|   | ||||
| @@ -16,10 +16,10 @@ import ( | ||||
| 	"code.gitea.io/gitea/models/organization" | ||||
| 	"code.gitea.io/gitea/models/unittest" | ||||
| 	user_model "code.gitea.io/gitea/models/user" | ||||
| 	"code.gitea.io/gitea/modules/translation/i18n" | ||||
| 	"code.gitea.io/gitea/services/auth" | ||||
|  | ||||
| 	"github.com/stretchr/testify/assert" | ||||
| 	"github.com/unknwon/i18n" | ||||
| ) | ||||
|  | ||||
| type ldapUser struct { | ||||
|   | ||||
| @@ -9,8 +9,9 @@ import ( | ||||
| 	"net/url" | ||||
| 	"testing" | ||||
|  | ||||
| 	"code.gitea.io/gitea/modules/translation/i18n" | ||||
|  | ||||
| 	"github.com/stretchr/testify/assert" | ||||
| 	"github.com/unknwon/i18n" | ||||
| ) | ||||
|  | ||||
| func TestViewBranches(t *testing.T) { | ||||
|   | ||||
| @@ -24,10 +24,10 @@ import ( | ||||
| 	"code.gitea.io/gitea/modules/git" | ||||
| 	api "code.gitea.io/gitea/modules/structs" | ||||
| 	"code.gitea.io/gitea/modules/test" | ||||
| 	"code.gitea.io/gitea/modules/translation/i18n" | ||||
| 	"code.gitea.io/gitea/services/pull" | ||||
|  | ||||
| 	"github.com/stretchr/testify/assert" | ||||
| 	"github.com/unknwon/i18n" | ||||
| ) | ||||
|  | ||||
| func testPullMerge(t *testing.T, session *TestSession, user, repo, pullnum string, mergeStyle repo_model.MergeStyle) *httptest.ResponseRecorder { | ||||
|   | ||||
| @@ -14,10 +14,10 @@ import ( | ||||
| 	"code.gitea.io/gitea/models/unittest" | ||||
| 	"code.gitea.io/gitea/modules/setting" | ||||
| 	"code.gitea.io/gitea/modules/test" | ||||
| 	"code.gitea.io/gitea/modules/translation/i18n" | ||||
|  | ||||
| 	"github.com/PuerkitoBio/goquery" | ||||
| 	"github.com/stretchr/testify/assert" | ||||
| 	"github.com/unknwon/i18n" | ||||
| ) | ||||
|  | ||||
| func createNewRelease(t *testing.T, session *TestSession, repoURL, tag, title string, preRelease, draft bool) { | ||||
|   | ||||
| @@ -13,9 +13,9 @@ import ( | ||||
|  | ||||
| 	"code.gitea.io/gitea/modules/setting" | ||||
| 	"code.gitea.io/gitea/modules/test" | ||||
| 	"code.gitea.io/gitea/modules/translation/i18n" | ||||
|  | ||||
| 	"github.com/stretchr/testify/assert" | ||||
| 	"github.com/unknwon/i18n" | ||||
| ) | ||||
|  | ||||
| func testCreateBranch(t testing.TB, session *TestSession, user, repo, oldRefSubURL, newBranchName string, expectedStatus int) string { | ||||
|   | ||||
| @@ -11,9 +11,9 @@ import ( | ||||
|  | ||||
| 	"code.gitea.io/gitea/models/unittest" | ||||
| 	user_model "code.gitea.io/gitea/models/user" | ||||
| 	"code.gitea.io/gitea/modules/translation/i18n" | ||||
|  | ||||
| 	"github.com/stretchr/testify/assert" | ||||
| 	"github.com/unknwon/i18n" | ||||
| ) | ||||
|  | ||||
| func testLoginFailed(t *testing.T, username, password, message string) { | ||||
|   | ||||
| @@ -13,9 +13,9 @@ import ( | ||||
| 	"code.gitea.io/gitea/models/unittest" | ||||
| 	user_model "code.gitea.io/gitea/models/user" | ||||
| 	"code.gitea.io/gitea/modules/setting" | ||||
| 	"code.gitea.io/gitea/modules/translation/i18n" | ||||
|  | ||||
| 	"github.com/stretchr/testify/assert" | ||||
| 	"github.com/unknwon/i18n" | ||||
| ) | ||||
|  | ||||
| func TestSignup(t *testing.T) { | ||||
| @@ -68,9 +68,9 @@ func TestSignupEmail(t *testing.T) { | ||||
| 		wantStatus int | ||||
| 		wantMsg    string | ||||
| 	}{ | ||||
| 		{"exampleUser@example.com\r\n", http.StatusOK, i18n.Tr("en", "form.email_invalid", nil)}, | ||||
| 		{"exampleUser@example.com\r", http.StatusOK, i18n.Tr("en", "form.email_invalid", nil)}, | ||||
| 		{"exampleUser@example.com\n", http.StatusOK, i18n.Tr("en", "form.email_invalid", nil)}, | ||||
| 		{"exampleUser@example.com\r\n", http.StatusOK, i18n.Tr("en", "form.email_invalid")}, | ||||
| 		{"exampleUser@example.com\r", http.StatusOK, i18n.Tr("en", "form.email_invalid")}, | ||||
| 		{"exampleUser@example.com\n", http.StatusOK, i18n.Tr("en", "form.email_invalid")}, | ||||
| 		{"exampleUser@example.com", http.StatusSeeOther, ""}, | ||||
| 	} | ||||
|  | ||||
|   | ||||
| @@ -11,9 +11,9 @@ import ( | ||||
| 	"code.gitea.io/gitea/models/unittest" | ||||
| 	user_model "code.gitea.io/gitea/models/user" | ||||
| 	"code.gitea.io/gitea/modules/test" | ||||
| 	"code.gitea.io/gitea/modules/translation/i18n" | ||||
|  | ||||
| 	"github.com/stretchr/testify/assert" | ||||
| 	"github.com/unknwon/i18n" | ||||
| ) | ||||
|  | ||||
| func TestViewUser(t *testing.T) { | ||||
|   | ||||
| @@ -10,19 +10,19 @@ import ( | ||||
| 	"net/url" | ||||
| 	"strings" | ||||
|  | ||||
| 	"github.com/unknwon/paginater" | ||||
| 	"code.gitea.io/gitea/modules/paginator" | ||||
| ) | ||||
|  | ||||
| // Pagination provides a pagination via Paginater and additional configurations for the link params used in rendering | ||||
| // Pagination provides a pagination via paginator.Paginator and additional configurations for the link params used in rendering | ||||
| type Pagination struct { | ||||
| 	Paginater *paginater.Paginater | ||||
| 	Paginater *paginator.Paginator | ||||
| 	urlParams []string | ||||
| } | ||||
|  | ||||
| // NewPagination creates a new instance of the Pagination struct | ||||
| func NewPagination(total, page, issueNum, numPages int) *Pagination { | ||||
| 	p := &Pagination{} | ||||
| 	p.Paginater = paginater.New(total, page, issueNum, numPages) | ||||
| 	p.Paginater = paginator.New(total, page, issueNum, numPages) | ||||
| 	return p | ||||
| } | ||||
|  | ||||
| @@ -53,5 +53,6 @@ func (p *Pagination) SetDefaultParams(ctx *Context) { | ||||
| 	p.AddParam(ctx, "sort", "SortType") | ||||
| 	p.AddParam(ctx, "q", "Keyword") | ||||
| 	p.AddParam(ctx, "tab", "TabName") | ||||
| 	// do not add any more uncommon params here! | ||||
| 	p.AddParam(ctx, "t", "queryType") | ||||
| } | ||||
|   | ||||
| @@ -8,7 +8,8 @@ import ( | ||||
| 	"fmt" | ||||
| 	"net/url" | ||||
|  | ||||
| 	"github.com/unknwon/i18n" | ||||
| 	"code.gitea.io/gitea/modules/translation/i18n" | ||||
|  | ||||
| 	"github.com/yuin/goldmark/ast" | ||||
| ) | ||||
|  | ||||
|   | ||||
							
								
								
									
										203
									
								
								modules/paginator/paginator.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										203
									
								
								modules/paginator/paginator.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,203 @@ | ||||
| // Copyright 2022 The Gitea Authors. | ||||
| // Copyright 2015 Unknwon. Licensed under the Apache License, Version 2.0 | ||||
|  | ||||
| package paginator | ||||
|  | ||||
| /* | ||||
| In template: | ||||
|  | ||||
| ```html | ||||
| {{if not .Page.IsFirst}}[First](1){{end}} | ||||
| {{if .Page.HasPrevious}}[Previous]({{.Page.Previous}}){{end}} | ||||
|  | ||||
| {{range .Page.Pages}} | ||||
| 	{{if eq .Num -1}} | ||||
| 	... | ||||
| 	{{else}} | ||||
| 	{{.Num}}{{if .IsCurrent}}(current){{end}} | ||||
| 	{{end}} | ||||
| {{end}} | ||||
|  | ||||
| {{if .Page.HasNext}}[Next]({{.Page.Next}}){{end}} | ||||
| {{if not .Page.IsLast}}[Last]({{.Page.TotalPages}}){{end}} | ||||
| ``` | ||||
|  | ||||
| Output: | ||||
|  | ||||
| ``` | ||||
| [First](1) [Previous](2) ... 2 3(current) 4 ... [Next](4) [Last](5) | ||||
| ``` | ||||
| */ | ||||
|  | ||||
| // Paginator represents a set of results of pagination calculations. | ||||
| type Paginator struct { | ||||
| 	total     int // total rows count | ||||
| 	pagingNum int // how many rows in one page | ||||
| 	current   int // current page number | ||||
| 	numPages  int // how many pages to show on the UI | ||||
| } | ||||
|  | ||||
| // New initialize a new pagination calculation and returns a Paginator as result. | ||||
| func New(total, pagingNum, current, numPages int) *Paginator { | ||||
| 	if pagingNum <= 0 { | ||||
| 		pagingNum = 1 | ||||
| 	} | ||||
| 	if current <= 0 { | ||||
| 		current = 1 | ||||
| 	} | ||||
| 	p := &Paginator{total, pagingNum, current, numPages} | ||||
| 	if p.current > p.TotalPages() { | ||||
| 		p.current = p.TotalPages() | ||||
| 	} | ||||
| 	return p | ||||
| } | ||||
|  | ||||
| // IsFirst returns true if current page is the first page. | ||||
| func (p *Paginator) IsFirst() bool { | ||||
| 	return p.current == 1 | ||||
| } | ||||
|  | ||||
| // HasPrevious returns true if there is a previous page relative to current page. | ||||
| func (p *Paginator) HasPrevious() bool { | ||||
| 	return p.current > 1 | ||||
| } | ||||
|  | ||||
| func (p *Paginator) Previous() int { | ||||
| 	if !p.HasPrevious() { | ||||
| 		return p.current | ||||
| 	} | ||||
| 	return p.current - 1 | ||||
| } | ||||
|  | ||||
| // HasNext returns true if there is a next page relative to current page. | ||||
| func (p *Paginator) HasNext() bool { | ||||
| 	return p.total > p.current*p.pagingNum | ||||
| } | ||||
|  | ||||
| func (p *Paginator) Next() int { | ||||
| 	if !p.HasNext() { | ||||
| 		return p.current | ||||
| 	} | ||||
| 	return p.current + 1 | ||||
| } | ||||
|  | ||||
| // IsLast returns true if current page is the last page. | ||||
| func (p *Paginator) IsLast() bool { | ||||
| 	if p.total == 0 { | ||||
| 		return true | ||||
| 	} | ||||
| 	return p.total > (p.current-1)*p.pagingNum && !p.HasNext() | ||||
| } | ||||
|  | ||||
| // Total returns number of total rows. | ||||
| func (p *Paginator) Total() int { | ||||
| 	return p.total | ||||
| } | ||||
|  | ||||
| // TotalPages returns number of total pages. | ||||
| func (p *Paginator) TotalPages() int { | ||||
| 	if p.total == 0 { | ||||
| 		return 1 | ||||
| 	} | ||||
| 	return (p.total + p.pagingNum - 1) / p.pagingNum | ||||
| } | ||||
|  | ||||
| // Current returns current page number. | ||||
| func (p *Paginator) Current() int { | ||||
| 	return p.current | ||||
| } | ||||
|  | ||||
| // PagingNum returns number of page size. | ||||
| func (p *Paginator) PagingNum() int { | ||||
| 	return p.pagingNum | ||||
| } | ||||
|  | ||||
| // Page presents a page in the paginator. | ||||
| type Page struct { | ||||
| 	num       int | ||||
| 	isCurrent bool | ||||
| } | ||||
|  | ||||
| func (p *Page) Num() int { | ||||
| 	return p.num | ||||
| } | ||||
|  | ||||
| func (p *Page) IsCurrent() bool { | ||||
| 	return p.isCurrent | ||||
| } | ||||
|  | ||||
| func getMiddleIdx(numPages int) int { | ||||
| 	return (numPages + 1) / 2 | ||||
| } | ||||
|  | ||||
| // Pages returns a list of nearby page numbers relative to current page. | ||||
| // If value is -1 means "..." that more pages are not showing. | ||||
| func (p *Paginator) Pages() []*Page { | ||||
| 	if p.numPages == 0 { | ||||
| 		return []*Page{} | ||||
| 	} else if p.numPages == 1 && p.TotalPages() == 1 { | ||||
| 		// Only show current page. | ||||
| 		return []*Page{{1, true}} | ||||
| 	} | ||||
|  | ||||
| 	// Total page number is less or equal. | ||||
| 	if p.TotalPages() <= p.numPages { | ||||
| 		pages := make([]*Page, p.TotalPages()) | ||||
| 		for i := range pages { | ||||
| 			pages[i] = &Page{i + 1, i+1 == p.current} | ||||
| 		} | ||||
| 		return pages | ||||
| 	} | ||||
|  | ||||
| 	numPages := p.numPages | ||||
| 	offsetIdx := 0 | ||||
| 	hasMoreNext := false | ||||
|  | ||||
| 	// Check more previous and next pages. | ||||
| 	previousNum := getMiddleIdx(p.numPages) - 1 | ||||
| 	if previousNum > p.current-1 { | ||||
| 		previousNum -= previousNum - (p.current - 1) | ||||
| 	} | ||||
| 	nextNum := p.numPages - previousNum - 1 | ||||
| 	if p.current+nextNum > p.TotalPages() { | ||||
| 		delta := nextNum - (p.TotalPages() - p.current) | ||||
| 		nextNum -= delta | ||||
| 		previousNum += delta | ||||
| 	} | ||||
|  | ||||
| 	offsetVal := p.current - previousNum | ||||
| 	if offsetVal > 1 { | ||||
| 		numPages++ | ||||
| 		offsetIdx = 1 | ||||
| 	} | ||||
|  | ||||
| 	if p.current+nextNum < p.TotalPages() { | ||||
| 		numPages++ | ||||
| 		hasMoreNext = true | ||||
| 	} | ||||
|  | ||||
| 	pages := make([]*Page, numPages) | ||||
|  | ||||
| 	// There are more previous pages. | ||||
| 	if offsetIdx == 1 { | ||||
| 		pages[0] = &Page{-1, false} | ||||
| 	} | ||||
| 	// There are more next pages. | ||||
| 	if hasMoreNext { | ||||
| 		pages[len(pages)-1] = &Page{-1, false} | ||||
| 	} | ||||
|  | ||||
| 	// Check previous pages. | ||||
| 	for i := 0; i < previousNum; i++ { | ||||
| 		pages[offsetIdx+i] = &Page{i + offsetVal, false} | ||||
| 	} | ||||
|  | ||||
| 	pages[offsetIdx+previousNum] = &Page{p.current, true} | ||||
|  | ||||
| 	// Check next pages. | ||||
| 	for i := 1; i <= nextNum; i++ { | ||||
| 		pages[offsetIdx+previousNum+i] = &Page{p.current + i, false} | ||||
| 	} | ||||
|  | ||||
| 	return pages | ||||
| } | ||||
							
								
								
									
										311
									
								
								modules/paginator/paginator_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										311
									
								
								modules/paginator/paginator_test.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,311 @@ | ||||
| // Copyright 2022 The Gitea Authors. | ||||
| // Copyright 2015 Unknwon. Licensed under the Apache License, Version 2.0 | ||||
|  | ||||
| package paginator | ||||
|  | ||||
| import ( | ||||
| 	"testing" | ||||
|  | ||||
| 	"github.com/stretchr/testify/assert" | ||||
| ) | ||||
|  | ||||
| func TestPaginator(t *testing.T) { | ||||
| 	t.Run("Basic logics", func(t *testing.T) { | ||||
| 		p := New(0, -1, -1, 0) | ||||
| 		assert.Equal(t, 1, p.PagingNum()) | ||||
| 		assert.True(t, p.IsFirst()) | ||||
| 		assert.False(t, p.HasPrevious()) | ||||
| 		assert.Equal(t, 1, p.Previous()) | ||||
| 		assert.False(t, p.HasNext()) | ||||
| 		assert.Equal(t, 1, p.Next()) | ||||
| 		assert.True(t, p.IsLast()) | ||||
| 		assert.Equal(t, 0, p.Total()) | ||||
|  | ||||
| 		p = New(1, 10, 2, 0) | ||||
| 		assert.Equal(t, 10, p.PagingNum()) | ||||
| 		assert.True(t, p.IsFirst()) | ||||
| 		assert.False(t, p.HasPrevious()) | ||||
| 		assert.False(t, p.HasNext()) | ||||
| 		assert.True(t, p.IsLast()) | ||||
|  | ||||
| 		p = New(10, 10, 1, 0) | ||||
| 		assert.Equal(t, 10, p.PagingNum()) | ||||
| 		assert.True(t, p.IsFirst()) | ||||
| 		assert.False(t, p.HasPrevious()) | ||||
| 		assert.False(t, p.HasNext()) | ||||
| 		assert.True(t, p.IsLast()) | ||||
|  | ||||
| 		p = New(11, 10, 1, 0) | ||||
| 		assert.Equal(t, 10, p.PagingNum()) | ||||
| 		assert.True(t, p.IsFirst()) | ||||
| 		assert.False(t, p.HasPrevious()) | ||||
| 		assert.True(t, p.HasNext()) | ||||
| 		assert.Equal(t, 2, p.Next()) | ||||
| 		assert.False(t, p.IsLast()) | ||||
|  | ||||
| 		p = New(11, 10, 2, 0) | ||||
| 		assert.Equal(t, 10, p.PagingNum()) | ||||
| 		assert.False(t, p.IsFirst()) | ||||
| 		assert.True(t, p.HasPrevious()) | ||||
| 		assert.Equal(t, 1, p.Previous()) | ||||
| 		assert.False(t, p.HasNext()) | ||||
| 		assert.True(t, p.IsLast()) | ||||
|  | ||||
| 		p = New(20, 10, 2, 0) | ||||
| 		assert.Equal(t, 10, p.PagingNum()) | ||||
| 		assert.False(t, p.IsFirst()) | ||||
| 		assert.True(t, p.HasPrevious()) | ||||
| 		assert.False(t, p.HasNext()) | ||||
| 		assert.True(t, p.IsLast()) | ||||
|  | ||||
| 		p = New(25, 10, 2, 0) | ||||
| 		assert.Equal(t, 10, p.PagingNum()) | ||||
| 		assert.False(t, p.IsFirst()) | ||||
| 		assert.True(t, p.HasPrevious()) | ||||
| 		assert.True(t, p.HasNext()) | ||||
| 		assert.False(t, p.IsLast()) | ||||
| 	}) | ||||
|  | ||||
| 	t.Run("Generate pages", func(t *testing.T) { | ||||
| 		p := New(0, 10, 1, 0) | ||||
| 		pages := p.Pages() | ||||
| 		assert.Equal(t, 0, len(pages)) | ||||
| 	}) | ||||
|  | ||||
| 	t.Run("Only current page", func(t *testing.T) { | ||||
| 		p := New(0, 10, 1, 1) | ||||
| 		pages := p.Pages() | ||||
| 		assert.Equal(t, 1, len(pages)) | ||||
| 		assert.Equal(t, 1, pages[0].Num()) | ||||
| 		assert.True(t, pages[0].IsCurrent()) | ||||
|  | ||||
| 		p = New(1, 10, 1, 1) | ||||
| 		pages = p.Pages() | ||||
| 		assert.Equal(t, 1, len(pages)) | ||||
| 		assert.Equal(t, 1, pages[0].Num()) | ||||
| 		assert.True(t, pages[0].IsCurrent()) | ||||
| 	}) | ||||
|  | ||||
| 	t.Run("Total page number is less or equal", func(t *testing.T) { | ||||
| 		p := New(1, 10, 1, 2) | ||||
| 		pages := p.Pages() | ||||
| 		assert.Equal(t, 1, len(pages)) | ||||
| 		assert.Equal(t, 1, pages[0].Num()) | ||||
| 		assert.True(t, pages[0].IsCurrent()) | ||||
|  | ||||
| 		p = New(11, 10, 1, 2) | ||||
| 		pages = p.Pages() | ||||
| 		assert.Equal(t, 2, len(pages)) | ||||
| 		assert.Equal(t, 1, pages[0].Num()) | ||||
| 		assert.True(t, pages[0].IsCurrent()) | ||||
| 		assert.Equal(t, 2, pages[1].Num()) | ||||
| 		assert.False(t, pages[1].IsCurrent()) | ||||
|  | ||||
| 		p = New(11, 10, 2, 2) | ||||
| 		pages = p.Pages() | ||||
| 		assert.Equal(t, 2, len(pages)) | ||||
| 		assert.Equal(t, 1, pages[0].Num()) | ||||
| 		assert.False(t, pages[0].IsCurrent()) | ||||
| 		assert.Equal(t, 2, pages[1].Num()) | ||||
| 		assert.True(t, pages[1].IsCurrent()) | ||||
|  | ||||
| 		p = New(25, 10, 2, 3) | ||||
| 		pages = p.Pages() | ||||
| 		assert.Equal(t, 3, len(pages)) | ||||
| 		assert.Equal(t, 1, pages[0].Num()) | ||||
| 		assert.False(t, pages[0].IsCurrent()) | ||||
| 		assert.Equal(t, 2, pages[1].Num()) | ||||
| 		assert.True(t, pages[1].IsCurrent()) | ||||
| 		assert.Equal(t, 3, pages[2].Num()) | ||||
| 		assert.False(t, pages[2].IsCurrent()) | ||||
| 	}) | ||||
|  | ||||
| 	t.Run("Has more previous pages ", func(t *testing.T) { | ||||
| 		// ... 2 | ||||
| 		p := New(11, 10, 2, 1) | ||||
| 		pages := p.Pages() | ||||
| 		assert.Equal(t, 2, len(pages)) | ||||
| 		assert.Equal(t, -1, pages[0].Num()) | ||||
| 		assert.False(t, pages[0].IsCurrent()) | ||||
| 		assert.Equal(t, 2, pages[1].Num()) | ||||
| 		assert.True(t, pages[1].IsCurrent()) | ||||
|  | ||||
| 		// ... 2 3 | ||||
| 		p = New(21, 10, 2, 2) | ||||
| 		pages = p.Pages() | ||||
| 		assert.Equal(t, 3, len(pages)) | ||||
| 		assert.Equal(t, -1, pages[0].Num()) | ||||
| 		assert.False(t, pages[0].IsCurrent()) | ||||
| 		assert.Equal(t, 2, pages[1].Num()) | ||||
| 		assert.True(t, pages[1].IsCurrent()) | ||||
| 		assert.Equal(t, 3, pages[2].Num()) | ||||
| 		assert.False(t, pages[2].IsCurrent()) | ||||
|  | ||||
| 		// ... 2 3 4 | ||||
| 		p = New(31, 10, 3, 3) | ||||
| 		pages = p.Pages() | ||||
| 		assert.Equal(t, 4, len(pages)) | ||||
| 		assert.Equal(t, -1, pages[0].Num()) | ||||
| 		assert.False(t, pages[0].IsCurrent()) | ||||
| 		assert.Equal(t, 2, pages[1].Num()) | ||||
| 		assert.False(t, pages[1].IsCurrent()) | ||||
| 		assert.Equal(t, 3, pages[2].Num()) | ||||
| 		assert.True(t, pages[2].IsCurrent()) | ||||
| 		assert.Equal(t, 4, pages[3].Num()) | ||||
| 		assert.False(t, pages[3].IsCurrent()) | ||||
|  | ||||
| 		// ... 3 4 5 | ||||
| 		p = New(41, 10, 4, 3) | ||||
| 		pages = p.Pages() | ||||
| 		assert.Equal(t, 4, len(pages)) | ||||
| 		assert.Equal(t, -1, pages[0].Num()) | ||||
| 		assert.False(t, pages[0].IsCurrent()) | ||||
| 		assert.Equal(t, 3, pages[1].Num()) | ||||
| 		assert.False(t, pages[1].IsCurrent()) | ||||
| 		assert.Equal(t, 4, pages[2].Num()) | ||||
| 		assert.True(t, pages[2].IsCurrent()) | ||||
| 		assert.Equal(t, 5, pages[3].Num()) | ||||
| 		assert.False(t, pages[3].IsCurrent()) | ||||
|  | ||||
| 		// ... 4 5 6 7 8 9 10 | ||||
| 		p = New(100, 10, 9, 7) | ||||
| 		pages = p.Pages() | ||||
| 		assert.Equal(t, 8, len(pages)) | ||||
| 		assert.Equal(t, -1, pages[0].Num()) | ||||
| 		assert.False(t, pages[0].IsCurrent()) | ||||
| 		assert.Equal(t, 4, pages[1].Num()) | ||||
| 		assert.False(t, pages[1].IsCurrent()) | ||||
| 		assert.Equal(t, 5, pages[2].Num()) | ||||
| 		assert.False(t, pages[2].IsCurrent()) | ||||
| 		assert.Equal(t, 6, pages[3].Num()) | ||||
| 		assert.False(t, pages[3].IsCurrent()) | ||||
| 		assert.Equal(t, 7, pages[4].Num()) | ||||
| 		assert.False(t, pages[4].IsCurrent()) | ||||
| 		assert.Equal(t, 8, pages[5].Num()) | ||||
| 		assert.False(t, pages[5].IsCurrent()) | ||||
| 		assert.Equal(t, 9, pages[6].Num()) | ||||
| 		assert.True(t, pages[6].IsCurrent()) | ||||
| 		assert.Equal(t, 10, pages[7].Num()) | ||||
| 		assert.False(t, pages[7].IsCurrent()) | ||||
| 	}) | ||||
|  | ||||
| 	t.Run("Has more next pages", func(t *testing.T) { | ||||
| 		// 1 ... | ||||
| 		p := New(21, 10, 1, 1) | ||||
| 		pages := p.Pages() | ||||
| 		assert.Equal(t, 2, len(pages)) | ||||
| 		assert.Equal(t, 1, pages[0].Num()) | ||||
| 		assert.True(t, pages[0].IsCurrent()) | ||||
| 		assert.Equal(t, -1, pages[1].Num()) | ||||
| 		assert.False(t, pages[1].IsCurrent()) | ||||
|  | ||||
| 		// 1 2 ... | ||||
| 		p = New(21, 10, 1, 2) | ||||
| 		pages = p.Pages() | ||||
| 		assert.Equal(t, 3, len(pages)) | ||||
| 		assert.Equal(t, 1, pages[0].Num()) | ||||
| 		assert.True(t, pages[0].IsCurrent()) | ||||
| 		assert.Equal(t, 2, pages[1].Num()) | ||||
| 		assert.False(t, pages[1].IsCurrent()) | ||||
| 		assert.Equal(t, -1, pages[2].Num()) | ||||
| 		assert.False(t, pages[2].IsCurrent()) | ||||
|  | ||||
| 		// 1 2 3 ... | ||||
| 		p = New(31, 10, 2, 3) | ||||
| 		pages = p.Pages() | ||||
| 		assert.Equal(t, 4, len(pages)) | ||||
| 		assert.Equal(t, 1, pages[0].Num()) | ||||
| 		assert.False(t, pages[0].IsCurrent()) | ||||
| 		assert.Equal(t, 2, pages[1].Num()) | ||||
| 		assert.True(t, pages[1].IsCurrent()) | ||||
| 		assert.Equal(t, 3, pages[2].Num()) | ||||
| 		assert.False(t, pages[2].IsCurrent()) | ||||
| 		assert.Equal(t, -1, pages[3].Num()) | ||||
| 		assert.False(t, pages[3].IsCurrent()) | ||||
|  | ||||
| 		// 1 2 3 ... | ||||
| 		p = New(41, 10, 2, 3) | ||||
| 		pages = p.Pages() | ||||
| 		assert.Equal(t, 4, len(pages)) | ||||
| 		assert.Equal(t, 1, pages[0].Num()) | ||||
| 		assert.False(t, pages[0].IsCurrent()) | ||||
| 		assert.Equal(t, 2, pages[1].Num()) | ||||
| 		assert.True(t, pages[1].IsCurrent()) | ||||
| 		assert.Equal(t, 3, pages[2].Num()) | ||||
| 		assert.False(t, pages[2].IsCurrent()) | ||||
| 		assert.Equal(t, -1, pages[3].Num()) | ||||
| 		assert.False(t, pages[3].IsCurrent()) | ||||
|  | ||||
| 		// 1 2 3 4 5 6 7 ... | ||||
| 		p = New(100, 10, 1, 7) | ||||
| 		pages = p.Pages() | ||||
| 		assert.Equal(t, 8, len(pages)) | ||||
| 		assert.Equal(t, 1, pages[0].Num()) | ||||
| 		assert.True(t, pages[0].IsCurrent()) | ||||
| 		assert.Equal(t, 2, pages[1].Num()) | ||||
| 		assert.False(t, pages[1].IsCurrent()) | ||||
| 		assert.Equal(t, 3, pages[2].Num()) | ||||
| 		assert.False(t, pages[2].IsCurrent()) | ||||
| 		assert.Equal(t, 4, pages[3].Num()) | ||||
| 		assert.False(t, pages[3].IsCurrent()) | ||||
| 		assert.Equal(t, 5, pages[4].Num()) | ||||
| 		assert.False(t, pages[4].IsCurrent()) | ||||
| 		assert.Equal(t, 6, pages[5].Num()) | ||||
| 		assert.False(t, pages[5].IsCurrent()) | ||||
| 		assert.Equal(t, 7, pages[6].Num()) | ||||
| 		assert.False(t, pages[6].IsCurrent()) | ||||
| 		assert.Equal(t, -1, pages[7].Num()) | ||||
| 		assert.False(t, pages[7].IsCurrent()) | ||||
|  | ||||
| 		// 1 2 3 4 5 6 7 ... | ||||
| 		p = New(100, 10, 2, 7) | ||||
| 		pages = p.Pages() | ||||
| 		assert.Equal(t, 8, len(pages)) | ||||
| 		assert.Equal(t, 1, pages[0].Num()) | ||||
| 		assert.False(t, pages[0].IsCurrent()) | ||||
| 		assert.Equal(t, 2, pages[1].Num()) | ||||
| 		assert.True(t, pages[1].IsCurrent()) | ||||
| 		assert.Equal(t, 3, pages[2].Num()) | ||||
| 		assert.False(t, pages[2].IsCurrent()) | ||||
| 		assert.Equal(t, 4, pages[3].Num()) | ||||
| 		assert.False(t, pages[3].IsCurrent()) | ||||
| 		assert.Equal(t, 5, pages[4].Num()) | ||||
| 		assert.False(t, pages[4].IsCurrent()) | ||||
| 		assert.Equal(t, 6, pages[5].Num()) | ||||
| 		assert.False(t, pages[5].IsCurrent()) | ||||
| 		assert.Equal(t, 7, pages[6].Num()) | ||||
| 		assert.False(t, pages[6].IsCurrent()) | ||||
| 		assert.Equal(t, -1, pages[7].Num()) | ||||
| 		assert.False(t, pages[7].IsCurrent()) | ||||
| 	}) | ||||
|  | ||||
| 	t.Run("Has both more previous and next pages", func(t *testing.T) { | ||||
| 		// ... 2 3 ... | ||||
| 		p := New(35, 10, 2, 2) | ||||
| 		pages := p.Pages() | ||||
| 		assert.Equal(t, 4, len(pages)) | ||||
| 		assert.Equal(t, -1, pages[0].Num()) | ||||
| 		assert.False(t, pages[0].IsCurrent()) | ||||
| 		assert.Equal(t, 2, pages[1].Num()) | ||||
| 		assert.True(t, pages[1].IsCurrent()) | ||||
| 		assert.Equal(t, 3, pages[2].Num()) | ||||
| 		assert.False(t, pages[2].IsCurrent()) | ||||
| 		assert.Equal(t, -1, pages[3].Num()) | ||||
| 		assert.False(t, pages[3].IsCurrent()) | ||||
|  | ||||
| 		// ... 2 3 4 ... | ||||
| 		p = New(49, 10, 3, 3) | ||||
| 		pages = p.Pages() | ||||
| 		assert.Equal(t, 5, len(pages)) | ||||
| 		assert.Equal(t, -1, pages[0].Num()) | ||||
| 		assert.False(t, pages[0].IsCurrent()) | ||||
| 		assert.Equal(t, 2, pages[1].Num()) | ||||
| 		assert.False(t, pages[1].IsCurrent()) | ||||
| 		assert.Equal(t, 3, pages[2].Num()) | ||||
| 		assert.True(t, pages[2].IsCurrent()) | ||||
| 		assert.Equal(t, 4, pages[3].Num()) | ||||
| 		assert.False(t, pages[3].IsCurrent()) | ||||
| 		assert.Equal(t, -1, pages[4].Num()) | ||||
| 		assert.False(t, pages[4].IsCurrent()) | ||||
| 	}) | ||||
| } | ||||
| @@ -12,8 +12,7 @@ import ( | ||||
| 	"time" | ||||
|  | ||||
| 	"code.gitea.io/gitea/modules/setting" | ||||
|  | ||||
| 	"github.com/unknwon/i18n" | ||||
| 	"code.gitea.io/gitea/modules/translation/i18n" | ||||
| ) | ||||
|  | ||||
| // Seconds-based time units | ||||
|   | ||||
| @@ -12,9 +12,9 @@ import ( | ||||
|  | ||||
| 	"code.gitea.io/gitea/modules/setting" | ||||
| 	"code.gitea.io/gitea/modules/translation" | ||||
| 	"code.gitea.io/gitea/modules/translation/i18n" | ||||
|  | ||||
| 	"github.com/stretchr/testify/assert" | ||||
| 	"github.com/unknwon/i18n" | ||||
| ) | ||||
|  | ||||
| var BaseDate time.Time | ||||
|   | ||||
							
								
								
									
										143
									
								
								modules/translation/i18n/i18n.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										143
									
								
								modules/translation/i18n/i18n.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,143 @@ | ||||
| // Copyright 2022 The Gitea Authors. All rights reserved. | ||||
| // Use of this source code is governed by a MIT-style | ||||
| // license that can be found in the LICENSE file. | ||||
|  | ||||
| package i18n | ||||
|  | ||||
| import ( | ||||
| 	"errors" | ||||
| 	"fmt" | ||||
| 	"reflect" | ||||
| 	"strings" | ||||
|  | ||||
| 	"code.gitea.io/gitea/modules/log" | ||||
|  | ||||
| 	"gopkg.in/ini.v1" | ||||
| ) | ||||
|  | ||||
| var ( | ||||
| 	ErrLocaleAlreadyExist = errors.New("lang already exists") | ||||
|  | ||||
| 	DefaultLocales = NewLocaleStore() | ||||
| ) | ||||
|  | ||||
| type locale struct { | ||||
| 	store    *LocaleStore | ||||
| 	langName string | ||||
| 	langDesc string | ||||
| 	messages *ini.File | ||||
| } | ||||
|  | ||||
| type LocaleStore struct { | ||||
| 	// at the moment, all these fields are readonly after initialization | ||||
| 	langNames   []string | ||||
| 	langDescs   []string | ||||
| 	localeMap   map[string]*locale | ||||
| 	defaultLang string | ||||
| } | ||||
|  | ||||
| func NewLocaleStore() *LocaleStore { | ||||
| 	return &LocaleStore{localeMap: make(map[string]*locale)} | ||||
| } | ||||
|  | ||||
| // AddLocaleByIni adds locale by ini into the store | ||||
| func (ls *LocaleStore) AddLocaleByIni(langName, langDesc string, localeFile interface{}, otherLocaleFiles ...interface{}) error { | ||||
| 	if _, ok := ls.localeMap[langName]; ok { | ||||
| 		return ErrLocaleAlreadyExist | ||||
| 	} | ||||
| 	iniFile, err := ini.LoadSources(ini.LoadOptions{ | ||||
| 		IgnoreInlineComment:         true, | ||||
| 		UnescapeValueCommentSymbols: true, | ||||
| 	}, localeFile, otherLocaleFiles...) | ||||
| 	if err == nil { | ||||
| 		iniFile.BlockMode = false | ||||
| 		lc := &locale{store: ls, langName: langName, langDesc: langDesc, messages: iniFile} | ||||
| 		ls.langNames = append(ls.langNames, lc.langName) | ||||
| 		ls.langDescs = append(ls.langDescs, lc.langDesc) | ||||
| 		ls.localeMap[lc.langName] = lc | ||||
| 	} | ||||
| 	return err | ||||
| } | ||||
|  | ||||
| func (ls *LocaleStore) HasLang(langName string) bool { | ||||
| 	_, ok := ls.localeMap[langName] | ||||
| 	return ok | ||||
| } | ||||
|  | ||||
| func (ls *LocaleStore) ListLangNameDesc() (names, desc []string) { | ||||
| 	return ls.langNames, ls.langDescs | ||||
| } | ||||
|  | ||||
| // SetDefaultLang sets default language as a fallback | ||||
| func (ls *LocaleStore) SetDefaultLang(lang string) { | ||||
| 	ls.defaultLang = lang | ||||
| } | ||||
|  | ||||
| // Tr translates content to target language. fall back to default language. | ||||
| func (ls *LocaleStore) Tr(lang, trKey string, trArgs ...interface{}) string { | ||||
| 	l, ok := ls.localeMap[lang] | ||||
| 	if !ok { | ||||
| 		l, ok = ls.localeMap[ls.defaultLang] | ||||
| 	} | ||||
| 	if ok { | ||||
| 		return l.Tr(trKey, trArgs...) | ||||
| 	} | ||||
| 	return trKey | ||||
| } | ||||
|  | ||||
| // Tr translates content to locale language. fall back to default language. | ||||
| func (l *locale) Tr(trKey string, trArgs ...interface{}) string { | ||||
| 	var section string | ||||
|  | ||||
| 	idx := strings.IndexByte(trKey, '.') | ||||
| 	if idx > 0 { | ||||
| 		section = trKey[:idx] | ||||
| 		trKey = trKey[idx+1:] | ||||
| 	} | ||||
|  | ||||
| 	trMsg := trKey | ||||
| 	if trIni, err := l.messages.Section(section).GetKey(trKey); err == nil { | ||||
| 		trMsg = trIni.Value() | ||||
| 	} else if l.store.defaultLang != "" && l.langName != l.store.defaultLang { | ||||
| 		// try to fall back to default | ||||
| 		if defaultLocale, ok := l.store.localeMap[l.store.defaultLang]; ok { | ||||
| 			if trIni, err = defaultLocale.messages.Section(section).GetKey(trKey); err == nil { | ||||
| 				trMsg = trIni.Value() | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	if len(trArgs) > 0 { | ||||
| 		fmtArgs := make([]interface{}, 0, len(trArgs)) | ||||
| 		for _, arg := range trArgs { | ||||
| 			val := reflect.ValueOf(arg) | ||||
| 			if val.Kind() == reflect.Slice { | ||||
| 				// before, it can accept Tr(lang, key, a, [b, c], d, [e, f]) as Sprintf(msg, a, b, c, d, e, f), it's an unstable behavior | ||||
| 				// now, we restrict the strange behavior and only support: | ||||
| 				// 1. Tr(lang, key, [slice-items]) as Sprintf(msg, items...) | ||||
| 				// 2. Tr(lang, key, args...) as Sprintf(msg, args...) | ||||
| 				if len(trArgs) == 1 { | ||||
| 					for i := 0; i < val.Len(); i++ { | ||||
| 						fmtArgs = append(fmtArgs, val.Index(i).Interface()) | ||||
| 					} | ||||
| 				} else { | ||||
| 					log.Error("the args for i18n shouldn't contain uncertain slices, key=%q, args=%v", trKey, trArgs) | ||||
| 					break | ||||
| 				} | ||||
| 			} else { | ||||
| 				fmtArgs = append(fmtArgs, arg) | ||||
| 			} | ||||
| 		} | ||||
| 		return fmt.Sprintf(trMsg, fmtArgs...) | ||||
| 	} | ||||
| 	return trMsg | ||||
| } | ||||
|  | ||||
| func ResetDefaultLocales() { | ||||
| 	DefaultLocales = NewLocaleStore() | ||||
| } | ||||
|  | ||||
| // Tr use default locales to translate content to target language. | ||||
| func Tr(lang, trKey string, trArgs ...interface{}) string { | ||||
| 	return DefaultLocales.Tr(lang, trKey, trArgs...) | ||||
| } | ||||
							
								
								
									
										56
									
								
								modules/translation/i18n/i18n_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										56
									
								
								modules/translation/i18n/i18n_test.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,56 @@ | ||||
| // Copyright 2022 The Gitea Authors. All rights reserved. | ||||
| // Use of this source code is governed by a MIT-style | ||||
| // license that can be found in the LICENSE file. | ||||
|  | ||||
| package i18n | ||||
|  | ||||
| import ( | ||||
| 	"testing" | ||||
|  | ||||
| 	"github.com/stretchr/testify/assert" | ||||
| ) | ||||
|  | ||||
| func Test_Tr(t *testing.T) { | ||||
| 	testData1 := []byte(` | ||||
| .dot.name = Dot Name | ||||
| fmt = %[1]s %[2]s | ||||
|  | ||||
| [section] | ||||
| sub = Sub String | ||||
| mixed = test value; <span style="color: red\; background: none;">more text</span> | ||||
| `) | ||||
|  | ||||
| 	testData2 := []byte(` | ||||
| fmt = %[2]s %[1]s | ||||
|  | ||||
| [section] | ||||
| sub = Changed Sub String | ||||
| `) | ||||
|  | ||||
| 	ls := NewLocaleStore() | ||||
| 	assert.NoError(t, ls.AddLocaleByIni("lang1", "Lang1", testData1)) | ||||
| 	assert.NoError(t, ls.AddLocaleByIni("lang2", "Lang2", testData2)) | ||||
| 	ls.SetDefaultLang("lang1") | ||||
|  | ||||
| 	result := ls.Tr("lang1", "fmt", "a", "b") | ||||
| 	assert.Equal(t, "a b", result) | ||||
|  | ||||
| 	result = ls.Tr("lang2", "fmt", "a", "b") | ||||
| 	assert.Equal(t, "b a", result) | ||||
|  | ||||
| 	result = ls.Tr("lang1", "section.sub") | ||||
| 	assert.Equal(t, "Sub String", result) | ||||
|  | ||||
| 	result = ls.Tr("lang2", "section.sub") | ||||
| 	assert.Equal(t, "Changed Sub String", result) | ||||
|  | ||||
| 	result = ls.Tr("", ".dot.name") | ||||
| 	assert.Equal(t, "Dot Name", result) | ||||
|  | ||||
| 	result = ls.Tr("lang2", "section.mixed") | ||||
| 	assert.Equal(t, `test value; <span style="color: red; background: none;">more text</span>`, result) | ||||
|  | ||||
| 	langs, descs := ls.ListLangNameDesc() | ||||
| 	assert.Equal(t, []string{"lang1", "lang2"}, langs) | ||||
| 	assert.Equal(t, []string{"Lang1", "Lang2"}, descs) | ||||
| } | ||||
| @@ -11,8 +11,8 @@ import ( | ||||
| 	"code.gitea.io/gitea/modules/log" | ||||
| 	"code.gitea.io/gitea/modules/options" | ||||
| 	"code.gitea.io/gitea/modules/setting" | ||||
| 	"code.gitea.io/gitea/modules/translation/i18n" | ||||
|  | ||||
| 	"github.com/unknwon/i18n" | ||||
| 	"golang.org/x/text/language" | ||||
| ) | ||||
|  | ||||
| @@ -54,13 +54,13 @@ func TryTr(lang, format string, args ...interface{}) (string, bool) { | ||||
|  | ||||
| // InitLocales loads the locales | ||||
| func InitLocales() { | ||||
| 	i18n.Reset() | ||||
| 	i18n.ResetDefaultLocales() | ||||
| 	localeNames, err := options.Dir("locale") | ||||
| 	if err != nil { | ||||
| 		log.Fatal("Failed to list locale files: %v", err) | ||||
| 	} | ||||
|  | ||||
| 	localFiles := make(map[string][]byte) | ||||
| 	localFiles := make(map[string][]byte, len(localeNames)) | ||||
| 	for _, name := range localeNames { | ||||
| 		localFiles[name], err = options.Locale(name) | ||||
| 		if err != nil { | ||||
| @@ -76,16 +76,21 @@ func InitLocales() { | ||||
| 	matcher = language.NewMatcher(supportedTags) | ||||
| 	for i := range setting.Names { | ||||
| 		key := "locale_" + setting.Langs[i] + ".ini" | ||||
| 		if err = i18n.SetMessageWithDesc(setting.Langs[i], setting.Names[i], localFiles[key]); err != nil { | ||||
| 		if err = i18n.DefaultLocales.AddLocaleByIni(setting.Langs[i], setting.Names[i], localFiles[key]); err != nil { | ||||
| 			log.Error("Failed to set messages to %s: %v", setting.Langs[i], err) | ||||
| 		} | ||||
| 	} | ||||
| 	i18n.SetDefaultLang("en-US") | ||||
| 	if len(setting.Langs) != 0 { | ||||
| 		defaultLangName := setting.Langs[0] | ||||
| 		if defaultLangName != "en-US" { | ||||
| 			log.Info("Use the first locale (%s) in LANGS setting option as default", defaultLangName) | ||||
| 		} | ||||
| 		i18n.DefaultLocales.SetDefaultLang(defaultLangName) | ||||
| 	} | ||||
|  | ||||
| 	allLangs = make([]*LangType, 0, i18n.Count()) | ||||
| 	langs, descs := i18n.DefaultLocales.ListLangNameDesc() | ||||
| 	allLangs = make([]*LangType, 0, len(langs)) | ||||
| 	allLangMap = map[string]*LangType{} | ||||
| 	langs := i18n.ListLangs() | ||||
| 	descs := i18n.ListLangDescs() | ||||
| 	for i, v := range langs { | ||||
| 		l := &LangType{v, descs[i]} | ||||
| 		allLangs = append(allLangs, l) | ||||
|   | ||||
| @@ -9,8 +9,8 @@ import ( | ||||
|  | ||||
| 	"code.gitea.io/gitea/modules/setting" | ||||
| 	"code.gitea.io/gitea/modules/translation" | ||||
| 	"code.gitea.io/gitea/modules/translation/i18n" | ||||
|  | ||||
| 	"github.com/unknwon/i18n" | ||||
| 	"golang.org/x/text/language" | ||||
| ) | ||||
|  | ||||
| @@ -28,8 +28,8 @@ func Locale(resp http.ResponseWriter, req *http.Request) translation.Locale { | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	// Check again in case someone modify by purpose. | ||||
| 	if lang != "" && !i18n.IsExist(lang) { | ||||
| 	// Check again in case someone changes the supported language list. | ||||
| 	if lang != "" && !i18n.DefaultLocales.HasLang(lang) { | ||||
| 		lang = "" | ||||
| 		changeLang = false | ||||
| 	} | ||||
|   | ||||
| @@ -18,9 +18,9 @@ import ( | ||||
| 	"code.gitea.io/gitea/modules/log" | ||||
| 	"code.gitea.io/gitea/modules/setting" | ||||
| 	"code.gitea.io/gitea/modules/timeutil" | ||||
| 	"code.gitea.io/gitea/modules/translation/i18n" | ||||
|  | ||||
| 	"github.com/sergi/go-diff/diffmatchpatch" | ||||
| 	"github.com/unknwon/i18n" | ||||
| ) | ||||
|  | ||||
| // GetContentHistoryOverview get overview | ||||
|   | ||||
| @@ -24,6 +24,7 @@ import ( | ||||
| 	"code.gitea.io/gitea/modules/context" | ||||
| 	"code.gitea.io/gitea/modules/log" | ||||
| 	"code.gitea.io/gitea/modules/setting" | ||||
| 	"code.gitea.io/gitea/modules/translation/i18n" | ||||
| 	"code.gitea.io/gitea/modules/typesniffer" | ||||
| 	"code.gitea.io/gitea/modules/util" | ||||
| 	"code.gitea.io/gitea/modules/web" | ||||
| @@ -31,8 +32,6 @@ import ( | ||||
| 	"code.gitea.io/gitea/services/agit" | ||||
| 	"code.gitea.io/gitea/services/forms" | ||||
| 	user_service "code.gitea.io/gitea/services/user" | ||||
|  | ||||
| 	"github.com/unknwon/i18n" | ||||
| ) | ||||
|  | ||||
| const ( | ||||
|   | ||||
| @@ -7,7 +7,7 @@ package cron | ||||
| import ( | ||||
| 	"time" | ||||
|  | ||||
| 	"github.com/unknwon/i18n" | ||||
| 	"code.gitea.io/gitea/modules/translation/i18n" | ||||
| ) | ||||
|  | ||||
| // Config represents a basic configuration interface that cron task | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 wxiaoguang
					wxiaoguang