mirror of
https://codeberg.org/forgejo/forgejo.git
synced 2025-08-26 20:23:49 +00:00
feat(build): improve lint-locale-usage
* add check for unused msgids * add ability to manually allowlist specific msgids and groups of msgids * lint-locale-usage should handle .locale.Tr and Unit{...} * lint-locale-usage should handle simple dynamic msgids * remove unnecessary DescKey and associated msgids + values
This commit is contained in:
parent
b0b6bd3658
commit
31190e385e
12 changed files with 365 additions and 146 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -55,6 +55,8 @@ cpu.out
|
|||
*.log
|
||||
*.log.*.gz
|
||||
|
||||
/build/lint-locale/lint-locale
|
||||
/build/lint-locale-usage/lint-locale-usage
|
||||
/gitea
|
||||
/gitea-vet
|
||||
/debug
|
||||
|
|
2
Makefile
2
Makefile
|
@ -460,7 +460,7 @@ lint-locale:
|
|||
|
||||
.PHONY: lint-locale-usage
|
||||
lint-locale-usage:
|
||||
$(GO) run build/lint-locale-usage/lint-locale-usage.go
|
||||
$(GO) run build/lint-locale-usage/lint-locale-usage.go --allow-masked-usages-from=build/lint-locale-usage/allowed-masked-usage.txt
|
||||
|
||||
.PHONY: lint-md
|
||||
lint-md: node_modules
|
||||
|
|
62
build/lint-locale-usage/allowed-masked-usage.txt
Normal file
62
build/lint-locale-usage/allowed-masked-usage.txt
Normal file
|
@ -0,0 +1,62 @@
|
|||
# translation tooling test keys
|
||||
meta.last_line
|
||||
translation_meta.test
|
||||
|
||||
# models/admin/task.go: instances of $TranslatableMessage.Format
|
||||
# this also gets instantiate as a Messenger once
|
||||
org.matrix.custom.html
|
||||
migrate.migrating_failed.error
|
||||
|
||||
# models/asymkey/gpg_key_object_verification.go: $ObjectVerification.Reason
|
||||
# unfortunately, it is non-trivial to parse all the occurences
|
||||
gpg.error.extract_sign
|
||||
gpg.error.failed_retrieval_gpg_keys
|
||||
gpg.error.generate_hash
|
||||
gpg.error.no_committer_account
|
||||
|
||||
# models/system/notice.go: func (n *Notice) TrStr() string
|
||||
admin.notices.type_1
|
||||
admin.notices.type_2
|
||||
|
||||
# modules/setting/ui.go
|
||||
themes.names.
|
||||
|
||||
# routers/web/repo/helper.go: "repo.tree_path_not_found_"+refType
|
||||
repo.tree_path_not_found_branch
|
||||
repo.tree_path_not_found_commit
|
||||
repo.tree_path_not_found_tag
|
||||
|
||||
# services/context/context.go
|
||||
relativetime.
|
||||
|
||||
# templates/mail/issue/default.tmpl: $.locale.Tr
|
||||
mail.issue.in_tree_path
|
||||
|
||||
# templates/org/team/new.tmpl: ctx.Locale.Tr (print "repo.permissions." $unit.Name ".read")}, and such
|
||||
repo.permissions.
|
||||
|
||||
# templates/package/metadata/arch.tmpl: $.locale.Tr
|
||||
packages.details.license
|
||||
|
||||
# templates/repo/editor/commit_form.tmpl: ctx.Locale.Tr (printf "repo.signing.wont_sign.%s" .CanCommitToBranch.WontSignReason)
|
||||
repo.signing.wont_sign.
|
||||
|
||||
# templates/repo/issue/view_content.tmpl: indirection via $closeTranslationKey
|
||||
repo.issues.close
|
||||
repo.pulls.close
|
||||
|
||||
# templates/repo/issue/view_content/comments.tmpl: indirection via $refTr
|
||||
repo.issues.ref_closing_from
|
||||
repo.issues.ref_issue_from
|
||||
repo.issues.ref_pull_from
|
||||
repo.issues.ref_reopening_from
|
||||
|
||||
# templates/repo/issue/view_content/comments.tmpl: ctx.Locale.Tr (printf "projects.type-%d.display_name" .OldProject.Type)
|
||||
projects.type-1.display_name
|
||||
projects.type-2.display_name
|
||||
projects.type-3.display_name
|
||||
|
||||
# modules/migration/messenger.go: invocations of Messenger
|
||||
# services/migrations/migrate.go: messenger(...)
|
||||
# *: repo.migrate.*.description (unknown where they come from)
|
||||
repo.migrate.
|
|
@ -5,6 +5,7 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"go/ast"
|
||||
goParser "go/parser"
|
||||
|
@ -12,6 +13,7 @@ import (
|
|||
"io/fs"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"text/template"
|
||||
|
@ -63,10 +65,120 @@ func InitLocaleTrFunctions() map[string][]uint {
|
|||
|
||||
type Handler struct {
|
||||
OnMsgid func(fset *token.FileSet, pos token.Pos, msgid string)
|
||||
OnMsgidPrefix func(fset *token.FileSet, pos token.Pos, msgidPrefix string)
|
||||
OnUnexpectedInvoke func(fset *token.FileSet, pos token.Pos, funcname string, argc int)
|
||||
OnWarning func(fset *token.FileSet, pos token.Pos, msg string)
|
||||
LocaleTrFunctions map[string][]uint
|
||||
}
|
||||
|
||||
type StringTrie interface {
|
||||
Matches(key []string) bool
|
||||
}
|
||||
|
||||
type StringTrieMap map[string]StringTrie
|
||||
|
||||
func (m *StringTrieMap) Matches(key []string) bool {
|
||||
if len(key) == 0 || m == nil {
|
||||
return true
|
||||
}
|
||||
value, ok := (*m)[key[0]]
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
if value == nil {
|
||||
return true
|
||||
}
|
||||
return value.Matches(key[1:])
|
||||
}
|
||||
|
||||
func (m *StringTrieMap) Insert(key []string) {
|
||||
if m == nil {
|
||||
return
|
||||
}
|
||||
|
||||
switch len(key) {
|
||||
case 0:
|
||||
return
|
||||
|
||||
case 1:
|
||||
(*m)[key[0]] = nil
|
||||
|
||||
default:
|
||||
if value, ok := (*m)[key[0]]; ok {
|
||||
if value == nil {
|
||||
return
|
||||
}
|
||||
} else {
|
||||
tmp := make(StringTrieMap)
|
||||
(*m)[key[0]] = &tmp
|
||||
}
|
||||
(*m)[key[0]].(*StringTrieMap).Insert(key[1:])
|
||||
}
|
||||
}
|
||||
|
||||
func DecodeKeyForStm(key string) []string {
|
||||
ret := strings.Split(key, ".")
|
||||
i := len(ret)
|
||||
for i > 0 && ret[i-1] == "" {
|
||||
i--
|
||||
}
|
||||
return ret[:i]
|
||||
}
|
||||
|
||||
func ParseAllowedMaskedUsages(fname string, usedMsgids *container.Set[string], allowedMaskedPrefixes *StringTrieMap) error {
|
||||
file, err := os.Open(fname)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
scanner := bufio.NewScanner(file)
|
||||
for scanner.Scan() {
|
||||
line := strings.TrimSpace(scanner.Text())
|
||||
if line == "" || strings.HasPrefix(line, "#") {
|
||||
continue
|
||||
}
|
||||
if strings.HasSuffix(line, ".") {
|
||||
allowedMaskedPrefixes.Insert(DecodeKeyForStm(line))
|
||||
} else {
|
||||
(*usedMsgids)[line] = struct{}{}
|
||||
}
|
||||
}
|
||||
return scanner.Err()
|
||||
}
|
||||
|
||||
func (handler Handler) HandleGoTrBasicLit(fset *token.FileSet, argLit *ast.BasicLit) {
|
||||
if argLit.Kind == token.STRING {
|
||||
// extract string content
|
||||
arg, err := strconv.Unquote(argLit.Value)
|
||||
if err == nil {
|
||||
// found interesting strings
|
||||
if strings.HasSuffix(arg, ".") || strings.HasSuffix(arg, "_") {
|
||||
handler.OnMsgidPrefix(fset, argLit.ValuePos, arg)
|
||||
} else {
|
||||
handler.OnMsgid(fset, argLit.ValuePos, arg)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (handler Handler) HandleGoTrArgument(fset *token.FileSet, n ast.Expr) {
|
||||
if argLit, ok := n.(*ast.BasicLit); ok {
|
||||
handler.HandleGoTrBasicLit(fset, argLit)
|
||||
} else if argBinExpr, ok := n.(*ast.BinaryExpr); ok {
|
||||
if argBinExpr.Op != token.ADD {
|
||||
// pass
|
||||
} else if argLit, ok := argBinExpr.X.(*ast.BasicLit); ok && argLit.Kind == token.STRING {
|
||||
// extract string content
|
||||
arg, err := strconv.Unquote(argLit.Value)
|
||||
if err == nil {
|
||||
// found interesting strings
|
||||
handler.OnMsgidPrefix(fset, argLit.ValuePos, arg)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// the `Handle*File` functions follow the following calling convention:
|
||||
// * `fname` is the name of the input file
|
||||
// * `src` is either `nil` (then the function invokes `ReadFile` to read the file)
|
||||
|
@ -74,7 +186,7 @@ type Handler struct {
|
|||
|
||||
func (handler Handler) HandleGoFile(fname string, src any) error {
|
||||
fset := token.NewFileSet()
|
||||
node, err := goParser.ParseFile(fset, fname, src, goParser.SkipObjectResolution)
|
||||
node, err := goParser.ParseFile(fset, fname, src, goParser.SkipObjectResolution|goParser.ParseComments)
|
||||
if err != nil {
|
||||
return LocatedError{
|
||||
Location: fname,
|
||||
|
@ -86,11 +198,7 @@ func (handler Handler) HandleGoFile(fname string, src any) error {
|
|||
ast.Inspect(node, func(n ast.Node) bool {
|
||||
// search for function calls of the form `anything.Tr(any-string-lit, ...)`
|
||||
|
||||
call, ok := n.(*ast.CallExpr)
|
||||
if !ok || len(call.Args) < 1 {
|
||||
return true
|
||||
}
|
||||
|
||||
if call, ok := n.(*ast.CallExpr); ok && len(call.Args) >= 1 {
|
||||
funSel, ok := call.Fun.(*ast.SelectorExpr)
|
||||
if !ok {
|
||||
return true
|
||||
|
@ -104,27 +212,100 @@ func (handler Handler) HandleGoFile(fname string, src any) error {
|
|||
var gotUnexpectedInvoke *int
|
||||
|
||||
for _, argNum := range ltf {
|
||||
if len(call.Args) >= int(argNum+1) {
|
||||
argLit, ok := call.Args[int(argNum)].(*ast.BasicLit)
|
||||
if !ok || argLit.Kind != token.STRING {
|
||||
continue
|
||||
}
|
||||
|
||||
// extract string content
|
||||
arg, err := strconv.Unquote(argLit.Value)
|
||||
if err == nil {
|
||||
// found interesting strings
|
||||
handler.OnMsgid(fset, argLit.ValuePos, arg)
|
||||
}
|
||||
} else {
|
||||
if len(call.Args) < int(argNum+1) {
|
||||
argc := len(call.Args)
|
||||
gotUnexpectedInvoke = &argc
|
||||
} else {
|
||||
handler.HandleGoTrArgument(fset, call.Args[int(argNum)])
|
||||
}
|
||||
}
|
||||
|
||||
if gotUnexpectedInvoke != nil {
|
||||
handler.OnUnexpectedInvoke(fset, funSel.Sel.NamePos, funSel.Sel.Name, *gotUnexpectedInvoke)
|
||||
}
|
||||
} else if composite, ok := n.(*ast.CompositeLit); ok {
|
||||
ident, ok := composite.Type.(*ast.Ident)
|
||||
if !ok {
|
||||
return true
|
||||
}
|
||||
|
||||
// special case: models/unit/unit.go
|
||||
if strings.HasSuffix(fname, "unit.go") && ident.Name == "Unit" && len(composite.Elts) == 6 {
|
||||
// NameKey has index 2
|
||||
// invoked like '{{ctx.Locale.Tr $unit.NameKey}}'
|
||||
nameKey, ok := composite.Elts[2].(*ast.BasicLit)
|
||||
if !ok || nameKey.Kind != token.STRING {
|
||||
handler.OnWarning(fset, composite.Elts[2].Pos(), "unexpected initialization of 'Unit'")
|
||||
return true
|
||||
}
|
||||
|
||||
// extract string content
|
||||
arg, err := strconv.Unquote(nameKey.Value)
|
||||
if err == nil {
|
||||
// found interesting strings
|
||||
handler.OnMsgid(fset, nameKey.ValuePos, arg)
|
||||
}
|
||||
}
|
||||
} else if function, ok := n.(*ast.FuncDecl); ok {
|
||||
matches := false
|
||||
if function.Doc != nil {
|
||||
for _, comment := range function.Doc.List {
|
||||
if strings.TrimSpace(comment.Text) == "//llu:returnsTrKey" {
|
||||
matches = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
if !matches {
|
||||
return true
|
||||
}
|
||||
results := function.Type.Results.List
|
||||
if len(results) != 1 {
|
||||
handler.OnWarning(fset, function.Type.Func, fmt.Sprintf("function %s has unexpected return type; expected single return value", function.Name.Name))
|
||||
return true
|
||||
}
|
||||
|
||||
ast.Inspect(function.Body, func(n ast.Node) bool {
|
||||
// search for return stmts
|
||||
// TODO: what about nested functions?
|
||||
if ret, ok := n.(*ast.ReturnStmt); ok {
|
||||
for _, res := range ret.Results {
|
||||
ast.Inspect(res, func(n ast.Node) bool {
|
||||
if expr, ok := n.(ast.Expr); ok {
|
||||
handler.HandleGoTrArgument(fset, expr)
|
||||
}
|
||||
return true
|
||||
})
|
||||
}
|
||||
return false
|
||||
}
|
||||
return true
|
||||
})
|
||||
return true
|
||||
} else if decl, ok := n.(*ast.GenDecl); ok && (decl.Tok == token.CONST || decl.Tok == token.VAR) {
|
||||
matches := false
|
||||
if decl.Doc != nil {
|
||||
for _, comment := range decl.Doc.List {
|
||||
if strings.TrimSpace(comment.Text) == "// llu:TrKeys" {
|
||||
matches = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
if !matches {
|
||||
return true
|
||||
}
|
||||
for _, spec := range decl.Specs {
|
||||
// interpret all contained strings as message IDs
|
||||
ast.Inspect(spec, func(n ast.Node) bool {
|
||||
if argLit, ok := n.(*ast.BasicLit); ok {
|
||||
handler.HandleGoTrBasicLit(fset, argLit)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
})
|
||||
|
@ -163,22 +344,26 @@ func (handler Handler) handleTemplateNode(fset *token.FileSet, node tmplParser.N
|
|||
return
|
||||
}
|
||||
|
||||
nodeChain, ok := nodeCommand.Args[0].(*tmplParser.ChainNode)
|
||||
if !ok {
|
||||
funcname := ""
|
||||
if nodeChain, ok := nodeCommand.Args[0].(*tmplParser.ChainNode); ok {
|
||||
if nodeIdent, ok := nodeChain.Node.(*tmplParser.IdentifierNode); ok {
|
||||
if nodeIdent.Ident != "ctx" || len(nodeChain.Field) != 2 || nodeChain.Field[0] != "Locale" {
|
||||
return
|
||||
}
|
||||
|
||||
nodeIdent, ok := nodeChain.Node.(*tmplParser.IdentifierNode)
|
||||
if !ok || nodeIdent.Ident != "ctx" || len(nodeChain.Field) != 2 || nodeChain.Field[0] != "Locale" {
|
||||
funcname = nodeChain.Field[1]
|
||||
}
|
||||
} else if nodeField, ok := nodeCommand.Args[0].(*tmplParser.FieldNode); ok {
|
||||
if len(nodeField.Ident) != 2 || !(nodeField.Ident[0] == "locale" || nodeField.Ident[0] == "Locale") {
|
||||
return
|
||||
}
|
||||
|
||||
ltf, ok := handler.LocaleTrFunctions[nodeChain.Field[1]]
|
||||
if !ok {
|
||||
return
|
||||
funcname = nodeField.Ident[1]
|
||||
}
|
||||
|
||||
var gotUnexpectedInvoke *int
|
||||
ltf, ok := handler.LocaleTrFunctions[funcname]
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
for _, argNum := range ltf {
|
||||
if len(nodeCommand.Args) >= int(argNum+2) {
|
||||
|
@ -195,7 +380,7 @@ func (handler Handler) handleTemplateNode(fset *token.FileSet, node tmplParser.N
|
|||
}
|
||||
|
||||
if gotUnexpectedInvoke != nil {
|
||||
handler.OnUnexpectedInvoke(fset, token.Pos(nodeChain.Pos), nodeChain.Field[1], *gotUnexpectedInvoke)
|
||||
handler.OnUnexpectedInvoke(fset, token.Pos(nodeCommand.Pos), funcname, *gotUnexpectedInvoke)
|
||||
}
|
||||
|
||||
default:
|
||||
|
@ -273,6 +458,7 @@ func (handler Handler) HandleTemplateFile(fname string, src any) error {
|
|||
// Possible command line flags:
|
||||
//
|
||||
// --allow-missing-msgids don't return an error code if missing message IDs are found
|
||||
// --allow-unused-msgids don't return an error code if unused message IDs are found
|
||||
//
|
||||
// EXIT CODES:
|
||||
//
|
||||
|
@ -281,13 +467,45 @@ func (handler Handler) HandleTemplateFile(fname string, src any) error {
|
|||
// 2 unable to parse locale ini/json files
|
||||
// 3 unable to parse go or text/template files
|
||||
// 4 found missing message IDs
|
||||
// 5 found unused message IDs
|
||||
// 6 invalid command line argument
|
||||
// 7 unable to parse allowed masked usages file
|
||||
//
|
||||
// SPECIAL GO DOC COMMENTS:
|
||||
//
|
||||
// //llu:returnsTrKey
|
||||
// can be used in front of functions to indicate
|
||||
// that the function returns message IDs
|
||||
//
|
||||
// // llu:TrKeys
|
||||
// can be used in front of 'const' and 'var' blocks
|
||||
// in order to mark all contained strings as message IDs
|
||||
//
|
||||
//nolint:forbidigo
|
||||
func main() {
|
||||
allowMissingMsgids := false
|
||||
allowUnusedMsgids := false
|
||||
usedMsgids := make(container.Set[string])
|
||||
allowedMaskedPrefixes := make(StringTrieMap)
|
||||
|
||||
for _, arg := range os.Args[1:] {
|
||||
if arg == "--allow-missing-msgids" {
|
||||
switch arg {
|
||||
case "--allow-missing-msgids":
|
||||
allowMissingMsgids = true
|
||||
|
||||
case "--allow-unused-msgids":
|
||||
allowUnusedMsgids = true
|
||||
|
||||
default:
|
||||
if argval, found := strings.CutPrefix(arg, "--allow-masked-usages-from="); found {
|
||||
if err := ParseAllowedMaskedUsages(argval, &usedMsgids, &allowedMaskedPrefixes); err != nil {
|
||||
fmt.Printf("%s:\tERROR: unable to parse masked usages: %s\n", argval, err.Error())
|
||||
os.Exit(7)
|
||||
}
|
||||
} else {
|
||||
fmt.Printf("<command line>:\tERROR: unknown argument: %s\n", arg)
|
||||
os.Exit(6)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -335,16 +553,25 @@ func main() {
|
|||
gotAnyMsgidError := false
|
||||
|
||||
handler := Handler{
|
||||
OnMsgidPrefix: func(fset *token.FileSet, pos token.Pos, msgidPrefix string) {
|
||||
// TODO: perhaps we should check if we have any strings with such a prefix, but that's slow...
|
||||
allowedMaskedPrefixes.Insert(DecodeKeyForStm(msgidPrefix))
|
||||
},
|
||||
OnMsgid: func(fset *token.FileSet, pos token.Pos, msgid string) {
|
||||
if !msgids.Contains(msgid) {
|
||||
gotAnyMsgidError = true
|
||||
fmt.Printf("%s:\tmissing msgid: %s\n", fset.Position(pos).String(), msgid)
|
||||
} else {
|
||||
usedMsgids[msgid] = struct{}{}
|
||||
}
|
||||
},
|
||||
OnUnexpectedInvoke: func(fset *token.FileSet, pos token.Pos, funcname string, argc int) {
|
||||
gotAnyMsgidError = true
|
||||
fmt.Printf("%s:\tunexpected invocation of %s with %d arguments\n", fset.Position(pos).String(), funcname, argc)
|
||||
},
|
||||
OnWarning: func(fset *token.FileSet, pos token.Pos, msg string) {
|
||||
fmt.Printf("%s:\tWARNING: %s\n", fset.Position(pos).String(), msg)
|
||||
},
|
||||
LocaleTrFunctions: InitLocaleTrFunctions(),
|
||||
}
|
||||
|
||||
|
@ -377,7 +604,27 @@ func main() {
|
|||
os.Exit(1)
|
||||
}
|
||||
|
||||
unusedMsgids := []string{}
|
||||
|
||||
for msgid := range msgids {
|
||||
if !usedMsgids.Contains(msgid) && !allowedMaskedPrefixes.Matches(DecodeKeyForStm(msgid)) {
|
||||
unusedMsgids = append(unusedMsgids, msgid)
|
||||
}
|
||||
}
|
||||
|
||||
sort.Strings(unusedMsgids)
|
||||
|
||||
if len(unusedMsgids) != 0 {
|
||||
fmt.Printf("=== unused msgids (%d): ===\n", len(unusedMsgids))
|
||||
for _, msgid := range unusedMsgids {
|
||||
fmt.Printf("- %s\n", msgid)
|
||||
}
|
||||
}
|
||||
|
||||
if !allowMissingMsgids && gotAnyMsgidError {
|
||||
os.Exit(4)
|
||||
}
|
||||
if !allowUnusedMsgids && len(unusedMsgids) != 0 {
|
||||
os.Exit(5)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -36,6 +36,7 @@ type ObjectVerification struct {
|
|||
TrustStatus string
|
||||
}
|
||||
|
||||
// llu:TrKeys
|
||||
const (
|
||||
// BadSignature is used as the reason when the signature has a KeyID that is in the db
|
||||
// but no key that has that ID verifies the signature. This is a suspicious failure.
|
||||
|
|
|
@ -469,6 +469,8 @@ func (issue *Issue) GetLastEventTimestamp() timeutil.TimeStamp {
|
|||
}
|
||||
|
||||
// GetLastEventLabel returns the localization label for the current issue.
|
||||
//
|
||||
//llu:returnsTrKey
|
||||
func (issue *Issue) GetLastEventLabel() string {
|
||||
if issue.IsClosed {
|
||||
if issue.IsPull && issue.PullRequest.HasMerged {
|
||||
|
@ -494,6 +496,8 @@ func (issue *Issue) GetLastComment(ctx context.Context) (*Comment, error) {
|
|||
}
|
||||
|
||||
// GetLastEventLabelFake returns the localization label for the current issue without providing a link in the username.
|
||||
//
|
||||
//llu:returnsTrKey
|
||||
func (issue *Issue) GetLastEventLabelFake() string {
|
||||
if issue.IsClosed {
|
||||
if issue.IsPull && issue.PullRequest.HasMerged {
|
||||
|
|
|
@ -48,6 +48,7 @@ const (
|
|||
AbuseCategoryTypeIllegalContent // 4
|
||||
)
|
||||
|
||||
// llu:TrKeys
|
||||
var AbuseCategoriesTranslationKeys = map[AbuseCategoryType]string{
|
||||
AbuseCategoryTypeSpam: "moderation.abuse_category.spam",
|
||||
AbuseCategoryTypeMalware: "moderation.abuse_category.malware",
|
||||
|
|
|
@ -182,6 +182,8 @@ func init() {
|
|||
}
|
||||
|
||||
// GetCardConfig retrieves the types of configurations project column cards could have
|
||||
//
|
||||
//llu:returnsTrKey
|
||||
func GetCardConfig() []CardConfig {
|
||||
return []CardConfig{
|
||||
{CardTypeTextOnly, "repo.projects.card_type.text_only"},
|
||||
|
|
|
@ -26,6 +26,8 @@ const (
|
|||
)
|
||||
|
||||
// GetTemplateConfigs retrieves the template configs of configurations project columns could have
|
||||
//
|
||||
//llu:returnsTrKey
|
||||
func GetTemplateConfigs() []TemplateConfig {
|
||||
return []TemplateConfig{
|
||||
{TemplateTypeNone, "repo.projects.type.none"},
|
||||
|
|
|
@ -271,7 +271,6 @@ type Unit struct {
|
|||
Name string
|
||||
NameKey string
|
||||
URI string
|
||||
DescKey string
|
||||
Idx int
|
||||
MaxAccessMode perm.AccessMode // The max access mode of the unit. i.e. Read means this unit can only be read.
|
||||
}
|
||||
|
@ -299,7 +298,6 @@ var (
|
|||
"code",
|
||||
"repo.code",
|
||||
"/",
|
||||
"repo.code.desc",
|
||||
0,
|
||||
perm.AccessModeOwner,
|
||||
}
|
||||
|
@ -309,7 +307,6 @@ var (
|
|||
"issues",
|
||||
"repo.issues",
|
||||
"/issues",
|
||||
"repo.issues.desc",
|
||||
1,
|
||||
perm.AccessModeOwner,
|
||||
}
|
||||
|
@ -319,7 +316,6 @@ var (
|
|||
"ext_issues",
|
||||
"repo.ext_issues",
|
||||
"/issues",
|
||||
"repo.ext_issues.desc",
|
||||
1,
|
||||
perm.AccessModeRead,
|
||||
}
|
||||
|
@ -329,7 +325,6 @@ var (
|
|||
"pulls",
|
||||
"repo.pulls",
|
||||
"/pulls",
|
||||
"repo.pulls.desc",
|
||||
2,
|
||||
perm.AccessModeOwner,
|
||||
}
|
||||
|
@ -339,7 +334,6 @@ var (
|
|||
"releases",
|
||||
"repo.releases",
|
||||
"/releases",
|
||||
"repo.releases.desc",
|
||||
3,
|
||||
perm.AccessModeOwner,
|
||||
}
|
||||
|
@ -349,7 +343,6 @@ var (
|
|||
"wiki",
|
||||
"repo.wiki",
|
||||
"/wiki",
|
||||
"repo.wiki.desc",
|
||||
4,
|
||||
perm.AccessModeOwner,
|
||||
}
|
||||
|
@ -359,7 +352,6 @@ var (
|
|||
"ext_wiki",
|
||||
"repo.ext_wiki",
|
||||
"/wiki",
|
||||
"repo.ext_wiki.desc",
|
||||
4,
|
||||
perm.AccessModeRead,
|
||||
}
|
||||
|
@ -369,7 +361,6 @@ var (
|
|||
"projects",
|
||||
"repo.projects",
|
||||
"/projects",
|
||||
"repo.projects.desc",
|
||||
5,
|
||||
perm.AccessModeOwner,
|
||||
}
|
||||
|
@ -379,7 +370,6 @@ var (
|
|||
"packages",
|
||||
"repo.packages",
|
||||
"/packages",
|
||||
"packages.desc",
|
||||
6,
|
||||
perm.AccessModeRead,
|
||||
}
|
||||
|
@ -389,7 +379,6 @@ var (
|
|||
"actions",
|
||||
"repo.actions",
|
||||
"/actions",
|
||||
"actions.unit.desc",
|
||||
7,
|
||||
perm.AccessModeOwner,
|
||||
}
|
||||
|
|
|
@ -15,7 +15,6 @@ version = Version
|
|||
powered_by = Powered by %s
|
||||
page = Page
|
||||
template = Template
|
||||
language = Language
|
||||
notifications = Notifications
|
||||
active_stopwatch = Active time tracker
|
||||
tracked_time_summary = Summary of tracked time based on filters of issue list
|
||||
|
@ -53,11 +52,7 @@ webauthn_error_empty = You must set a name for this key.
|
|||
webauthn_error_timeout = Timeout reached before your key could be read. Please reload this page and retry.
|
||||
|
||||
repository = Repository
|
||||
organization = Organization
|
||||
mirror = Mirror
|
||||
new_mirror = New mirror
|
||||
new_fork = New repository fork
|
||||
new_project = New project
|
||||
new_project_column = New column
|
||||
admin_panel = Site administration
|
||||
settings = Settings
|
||||
|
@ -104,7 +99,6 @@ disabled = Disabled
|
|||
locked = Locked
|
||||
|
||||
copy = Copy
|
||||
copy_generic = Copy to clipboard
|
||||
copy_url = Copy URL
|
||||
copy_hash = Copy hash
|
||||
copy_path = Copy path
|
||||
|
@ -168,14 +162,6 @@ filter.private = Private
|
|||
[search]
|
||||
search = Search…
|
||||
type_tooltip = Search type
|
||||
fuzzy = Fuzzy
|
||||
fuzzy_tooltip = Include results that also match the search term closely
|
||||
union = Union
|
||||
union_tooltip = Include results that match any of the whitespace separated keywords
|
||||
exact = Exact
|
||||
exact_tooltip = Include only results that match the exact search term
|
||||
regexp = RegExp
|
||||
regexp_tooltip = Interpret the search term as a regular expression
|
||||
repo_kind = Search repos…
|
||||
user_kind = Search users…
|
||||
org_kind = Search orgs…
|
||||
|
@ -367,7 +353,6 @@ save_config_failed = Failed to save configuration: %v
|
|||
enable_update_checker_helper_forgejo = It will periodically check for new Forgejo versions by checking a TXT DNS record at release.forgejo.org.
|
||||
invalid_admin_setting = Administrator account setting is invalid: %v
|
||||
invalid_log_root_path = The log path is invalid: %v
|
||||
allow_dots_in_usernames = Allow users to use dots in their usernames. Doesn't affect existing accounts.
|
||||
no_reply_address = Hidden email domain
|
||||
no_reply_address_helper = Domain name for users with a hidden email address. For example, the username "joe" will be logged in Git as "joe@noreply.example.org" if the hidden email domain is set to "noreply.example.org".
|
||||
password_algorithm = Password hash algorithm
|
||||
|
@ -535,8 +520,6 @@ totp_enrolled.subject = You have activated TOTP as 2FA method
|
|||
totp_enrolled.text_1.no_webauthn = You have just enabled TOTP for your account. This means that for all future logins to your account, you must use TOTP as a 2FA method.
|
||||
totp_enrolled.text_1.has_webauthn = You have just enabled TOTP for your account. This means that for all future logins to your account, you could use TOTP as a 2FA method or use any of your security keys.
|
||||
|
||||
register_success = Registration successful
|
||||
|
||||
issue_assigned.pull = @%[1]s assigned you to pull request %[2]s in repository %[3]s.
|
||||
issue_assigned.issue = @%[1]s assigned you to issue %[2]s in repository %[3]s.
|
||||
|
||||
|
@ -581,7 +564,6 @@ yes = Yes
|
|||
no = No
|
||||
confirm = Confirm
|
||||
cancel = Cancel
|
||||
modify = Update
|
||||
|
||||
[form]
|
||||
UserName = Username
|
||||
|
@ -733,17 +715,14 @@ form.name_chars_not_allowed = Username "%s" contains invalid characters.
|
|||
profile = Profile
|
||||
account = Account
|
||||
appearance = Appearance
|
||||
password = Password
|
||||
security = Security
|
||||
avatar = Avatar
|
||||
ssh_gpg_keys = SSH / GPG keys
|
||||
applications = Applications
|
||||
orgs = Organizations
|
||||
repos = Repositories
|
||||
delete = Delete account
|
||||
twofa = Two-factor authentication (TOTP)
|
||||
organization = Organizations
|
||||
uid = UID
|
||||
webauthn = Two-factor authentication (Security keys)
|
||||
blocked_users = Blocked users
|
||||
storage_overview = Storage overview
|
||||
|
@ -765,12 +744,10 @@ update_language = Change language
|
|||
update_language_not_found = Language "%s" is not available.
|
||||
update_language_success = Language has been updated.
|
||||
update_profile_success = Your profile has been updated.
|
||||
change_username = Your username has been changed.
|
||||
change_username_prompt = Note: Changing your username also changes your account URL.
|
||||
change_username_redirect_prompt = The old username will redirect until someone claims it.
|
||||
change_username_redirect_prompt.with_cooldown.one = The old username will be available to everyone after a cooldown period of %[1]d day. You can still reclaim the old username during the cooldown period.
|
||||
change_username_redirect_prompt.with_cooldown.few = The old username will be available to everyone after a cooldown period of %[1]d days. You can still reclaim the old username during the cooldown period.
|
||||
continue = Continue
|
||||
cancel = Cancel
|
||||
language = Language
|
||||
language.title = Default language
|
||||
|
@ -961,9 +938,7 @@ permissions_list = Permissions:
|
|||
|
||||
manage_oauth2_applications = Manage OAuth2 applications
|
||||
edit_oauth2_application = Edit OAuth2 Application
|
||||
oauth2_applications_desc = OAuth2 applications enables your third-party application to securely authenticate users at this Forgejo instance.
|
||||
remove_oauth2_application = Remove OAuth2 Application
|
||||
remove_oauth2_application_desc = Removing an OAuth2 application will revoke access to all signed access tokens. Continue?
|
||||
remove_oauth2_application_success = The application has been deleted.
|
||||
create_oauth2_application = Create a new OAuth2 application
|
||||
create_oauth2_application_button = Create application
|
||||
|
@ -977,7 +952,6 @@ oauth2_client_id = Client ID
|
|||
oauth2_client_secret = Client secret
|
||||
oauth2_regenerate_secret = Regenerate secret
|
||||
oauth2_regenerate_secret_hint = Lost your secret?
|
||||
oauth2_client_secret_hint = The secret will not be shown again after you leave or refresh this page. Please ensure that you have saved it.
|
||||
oauth2_application_edit = Edit
|
||||
oauth2_application_create_description = OAuth2 applications gives your third-party application access to user accounts on this instance.
|
||||
oauth2_application_remove_description = Removing an OAuth2 application will prevent it from accessing authorized user accounts on this instance. Continue?
|
||||
|
@ -1118,11 +1092,8 @@ open_with_editor = Open with %s
|
|||
download_zip = Download ZIP
|
||||
download_tar = Download TAR.GZ
|
||||
download_bundle = Download BUNDLE
|
||||
generate_repo = Generate repository
|
||||
generate_from = Generate from
|
||||
repo_desc = Description
|
||||
repo_desc_helper = Enter short description (optional)
|
||||
repo_lang = Language
|
||||
repo_gitignore_helper = Select .gitignore templates
|
||||
repo_gitignore_helper_desc = Choose which files not to track from a list of templates for common languages. Typical artifacts generated by each language's build tools are included on .gitignore by default.
|
||||
issue_labels = Labels
|
||||
|
@ -1160,7 +1131,6 @@ mirror_lfs = Large File Storage (LFS)
|
|||
mirror_lfs_desc = Activate mirroring of LFS data.
|
||||
mirror_lfs_endpoint = LFS endpoint
|
||||
mirror_lfs_endpoint_desc = Sync will attempt to use the clone url to <a target="_blank" rel="noopener noreferrer" href="%s">determine the LFS server</a>. You can also specify a custom endpoint if the repository LFS data is stored somewhere else.
|
||||
mirror_last_synced = Last synchronized
|
||||
mirror_password_placeholder = (Unchanged)
|
||||
mirror_password_blank_placeholder = (Unset)
|
||||
mirror_password_help = Change the username to erase a stored password.
|
||||
|
@ -1169,7 +1139,6 @@ stargazers = Stargazers
|
|||
stars_remove_warning = This will remove all stars from this repository.
|
||||
forks = Forks
|
||||
stars = Stars
|
||||
reactions_more = and %d more
|
||||
unit_disabled = The site administrator has disabled this repository section.
|
||||
language_other = Other
|
||||
adopt_search = Enter username to search for unadopted repositories… (leave blank to find all)
|
||||
|
@ -1199,7 +1168,6 @@ transfer.no_permission_to_accept = You do not have permission to accept this tra
|
|||
transfer.no_permission_to_reject = You do not have permission to reject this transfer.
|
||||
|
||||
desc.private = Private
|
||||
desc.public = Public
|
||||
desc.template = Template
|
||||
desc.internal = Internal
|
||||
desc.archived = Archived
|
||||
|
@ -1301,7 +1269,6 @@ unwatch = Unwatch
|
|||
star = Star
|
||||
unstar = Unstar
|
||||
fork = Fork
|
||||
download_archive = Download repository
|
||||
more_operations = More operations
|
||||
|
||||
no_desc = No description
|
||||
|
@ -1315,29 +1282,22 @@ broken_message = The Git data underlying this repository cannot be read. Contact
|
|||
|
||||
code = Code
|
||||
code.desc = Access source code, files, commits and branches.
|
||||
branch = Branch
|
||||
tree = Tree
|
||||
clear_ref = `Clear current reference`
|
||||
filter_branch_and_tag = Filter branch or tag
|
||||
find_tag = Find tag
|
||||
branches = Branches
|
||||
tag = Tag
|
||||
tags = Tags
|
||||
issues = Issues
|
||||
pulls = Pull requests
|
||||
project = Projects
|
||||
packages = Packages
|
||||
actions = Actions
|
||||
release = Release
|
||||
releases = Releases
|
||||
labels = Labels
|
||||
milestones = Milestones
|
||||
org_labels_desc = Organization level labels that can be used with <strong>all repositories</strong> under this organization
|
||||
org_labels_desc_manage = manage
|
||||
|
||||
commits = Commits
|
||||
commit = Commit
|
||||
|
||||
n_commit_one=%s commit
|
||||
n_commit_few=%s commits
|
||||
n_branch_one=%s branch
|
||||
|
@ -1431,7 +1391,6 @@ editor.propose_file_change = Propose file change
|
|||
editor.new_branch_name = Name the new branch for this commit
|
||||
editor.new_branch_name_desc = New branch name…
|
||||
editor.cancel = Cancel
|
||||
editor.filename_cannot_be_empty = The filename cannot be empty.
|
||||
editor.filename_is_invalid = The filename is invalid: "%s".
|
||||
editor.invalid_commit_mail = Invalid mail for creating a commit.
|
||||
editor.branch_does_not_exist = Branch "%s" does not exist in this repository.
|
||||
|
@ -1465,7 +1424,6 @@ editor.cherry_pick = Cherry-pick %s onto:
|
|||
editor.revert = Revert %s onto:
|
||||
editor.commit_email = Commit email
|
||||
|
||||
commits.desc = Browse source code change history.
|
||||
commits.commits = Commits
|
||||
commits.no_commits = No commits in common. "%s" and "%s" have entirely different histories.
|
||||
commits.nothing_to_compare = These branches are equal.
|
||||
|
@ -1477,8 +1435,6 @@ commits.message = Message
|
|||
commits.browse_further = Browse further
|
||||
commits.renamed_from = Renamed from %s
|
||||
commits.date = Date
|
||||
commits.older = Older
|
||||
commits.newer = Newer
|
||||
commits.signed_by = Signed by
|
||||
commits.signed_by_untrusted_user = Signed by untrusted user
|
||||
commits.signed_by_untrusted_user_unmatched = Signed by untrusted user who does not match committer
|
||||
|
@ -1503,7 +1459,6 @@ commitstatus.success = Success
|
|||
ext_issues = External issues
|
||||
|
||||
projects = Projects
|
||||
projects.desc = Manage issues and pulls in project boards.
|
||||
projects.description = Description (optional)
|
||||
projects.description_placeholder = Description
|
||||
projects.create = Create project
|
||||
|
@ -1540,7 +1495,6 @@ projects.card_type.desc = Card previews
|
|||
projects.card_type.images_and_text = Images and text
|
||||
projects.card_type.text_only = Text only
|
||||
|
||||
issues.desc = Organize bug reports, tasks and milestones.
|
||||
issues.filter_assignees = Filter assignee
|
||||
issues.filter_milestones = Filter milestone
|
||||
issues.filter_projects = Filter project
|
||||
|
@ -1583,7 +1537,6 @@ issues.new_label = New label
|
|||
issues.new_label_placeholder = Label name
|
||||
issues.new_label_desc_placeholder = Description
|
||||
issues.create_label = Create label
|
||||
issues.label_templates.title = Load a label preset
|
||||
issues.label_templates.info = No labels exist yet. Create a label with "New label" or use a label preset:
|
||||
issues.label_templates.helper = Select a label preset
|
||||
issues.label_templates.use = Use label preset
|
||||
|
@ -1628,7 +1581,6 @@ issues.filter_assginee_no_assignee = No assignee
|
|||
issues.filter_poster = Author
|
||||
issues.filter_poster_no_select = All authors
|
||||
issues.filter_type = Type
|
||||
issues.filter_type.all_issues = All issues
|
||||
issues.filter_type.all_pull_requests = All pull requests
|
||||
issues.filter_type.assigned_to_you = Assigned to you
|
||||
issues.filter_type.created_by_you = Created by you
|
||||
|
@ -1751,7 +1703,6 @@ issues.label.filter_sort.reverse_by_size = Largest size
|
|||
issues.num_participants_one = %d participant
|
||||
issues.num_participants_few = %d participants
|
||||
issues.attachment.open_tab = `Click to see "%s" in a new tab`
|
||||
issues.attachment.download = `Click to download "%s"`
|
||||
issues.subscribe = Subscribe
|
||||
issues.unsubscribe = Unsubscribe
|
||||
issues.unpin_issue = Unpin issue
|
||||
|
@ -1799,7 +1750,6 @@ issues.del_time_history= `deleted spent time %s`
|
|||
issues.add_time_hours = Hours
|
||||
issues.add_time_minutes = Minutes
|
||||
issues.add_time_sum_to_small = No time was entered.
|
||||
issues.time_spent_total = Total time spent
|
||||
issues.time_spent_from_all_authors = `Total time spent: %s`
|
||||
issues.due_date = Due date
|
||||
issues.push_commit_1 = added %d commit %s
|
||||
|
@ -1855,7 +1805,6 @@ issues.review.dismissed_label = Dismissed
|
|||
issues.review.left_comment = left a comment
|
||||
issues.review.content.empty = You need to leave a comment indicating the requested change(s).
|
||||
issues.review.reject = requested changes %s
|
||||
issues.review.wait = was requested for review %s
|
||||
issues.review.add_review_request = requested review from %[1]s %[2]s
|
||||
issues.review.add_review_requests = requested reviews from %[1]s %[2]s
|
||||
issues.review.remove_review_request = removed review request for %[1]s %[2]s
|
||||
|
@ -1885,13 +1834,11 @@ issues.content_history.delete_from_history_confirm = Delete from history?
|
|||
issues.content_history.options = Options
|
||||
issues.blocked_by_user = You cannot create issues in this repository because you are blocked by the repository owner.
|
||||
comment.blocked_by_user = Commenting is not possible because you are blocked by the repository owner or the author.
|
||||
issues.reopen.blocked_by_user = You cannot reopen this issue because you are blocked by the repository owner or the poster of this issue.
|
||||
issues.summary_card_alt = Summary card of an issue titled "%s" in repository %s
|
||||
|
||||
compare.compare_base = base
|
||||
compare.compare_head = compare
|
||||
|
||||
pulls.desc = Enable pull requests and code reviews.
|
||||
pulls.new = New pull request
|
||||
pulls.view = View pull request
|
||||
pulls.edit.already_changed = Unable to save changes to the pull request. It appears the content has already been changed by another user. Please refresh the page and try editing again to avoid overwriting their changes
|
||||
|
@ -1917,7 +1864,6 @@ pulls.show_changes_since_your_last_review = Show changes since your last review
|
|||
pulls.showing_only_single_commit = Showing only changes of commit %[1]s
|
||||
pulls.showing_specified_commit_range = Showing only changes between %[1]s..%[2]s
|
||||
pulls.select_commit_hold_shift_for_range = Select commit. Hold shift + click to select a range
|
||||
pulls.review_only_possible_for_full_diff = Review is only possible when viewing the full diff
|
||||
pulls.filter_changes_by_commit = Filter by commit
|
||||
pulls.nothing_to_compare = These branches are equal. There is no need to create a pull request.
|
||||
pulls.nothing_to_compare_have_tag = The selected branches/tags are equal.
|
||||
|
@ -2107,7 +2053,6 @@ ext_wiki = External Wiki
|
|||
wiki = Wiki
|
||||
wiki.welcome = Welcome to the wiki.
|
||||
wiki.welcome_desc = The wiki lets you write and share documentation with collaborators.
|
||||
wiki.desc = Write and share documentation with collaborators.
|
||||
wiki.create_first_page = Create the first page
|
||||
wiki.page = Page
|
||||
wiki.filter_page = Filter page
|
||||
|
@ -2737,11 +2682,9 @@ diff.protected = Protected
|
|||
diff.image.side_by_side = Side by side
|
||||
diff.image.swipe = Swipe
|
||||
diff.image.overlay = Overlay
|
||||
diff.has_escaped = This line has hidden Unicode characters
|
||||
diff.show_file_tree = Show file tree
|
||||
diff.hide_file_tree = Hide file tree
|
||||
|
||||
releases.desc = Track project versions and downloads.
|
||||
release.releases = Releases
|
||||
release.detail = Release details
|
||||
release.tags = Tags
|
||||
|
@ -2753,7 +2696,6 @@ release.compare = Compare
|
|||
release.edit = Edit
|
||||
release.ahead.commits = <strong>%d</strong> commits
|
||||
release.ahead.target = to %s since this release
|
||||
tag.ahead.target = to %s since this tag
|
||||
release.source_code = Source code
|
||||
release.new_subheader = Releases organize project versions.
|
||||
release.edit_subheader = Releases organize project versions.
|
||||
|
@ -2781,7 +2723,6 @@ release.deletion_tag_success = The tag has been deleted.
|
|||
release.tag_name_already_exist = A release with this tag name already exists.
|
||||
release.tag_name_invalid = The tag name is not valid.
|
||||
release.tag_name_protected = The tag name is protected.
|
||||
release.tag_already_exist = This tag name already exists.
|
||||
release.downloads = Downloads
|
||||
release.download_count_one = %s download
|
||||
release.download_count_few = %s downloads
|
||||
|
@ -2802,7 +2743,6 @@ release.summary_card_alt = Summary card of an release titled "%s" in repository
|
|||
|
||||
branch.name = Branch name
|
||||
branch.already_exists = A branch named "%s" already exists.
|
||||
branch.delete_head = Delete
|
||||
branch.delete = Delete branch "%s"
|
||||
branch.delete_html = Delete branch
|
||||
branch.delete_desc = Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
|
||||
|
@ -2842,7 +2782,6 @@ tag.create_tag_from = Create new tag from "%s"
|
|||
tag.create_success = Tag "%s" has been created.
|
||||
|
||||
topic.manage_topics = Manage topics
|
||||
topic.done = Done
|
||||
topic.count_prompt = You cannot select more than 25 topics
|
||||
topic.format_prompt = Topics must start with a letter or number, can include dashes ("-") and dots ("."), can be up to 35 characters long. Letters must be lowercase.
|
||||
|
||||
|
@ -2913,7 +2852,6 @@ form.create_org_not_allowed = You are not allowed to create an organization.
|
|||
|
||||
settings = Settings
|
||||
settings.options = Organization
|
||||
settings.full_name = Full name
|
||||
settings.email = Contact email
|
||||
settings.website = Website
|
||||
settings.location = Location
|
||||
|
@ -2943,8 +2881,6 @@ settings.hooks_desc = Add webhooks which will be triggered for <strong>all repos
|
|||
|
||||
settings.labels_desc = Add labels which can be used on issues for <strong>all repositories</strong> under this organization.
|
||||
|
||||
members.membership_visibility = Membership visibility:
|
||||
members.public = Visible
|
||||
members.public_helper = Make hidden
|
||||
members.private = Hidden
|
||||
members.private_helper = Make visible
|
||||
|
@ -2955,8 +2891,6 @@ members.remove = Remove
|
|||
members.remove.detail = Remove %[1]s from %[2]s?
|
||||
members.leave = Leave
|
||||
members.leave.detail = Are you sure you want to leave organization "%s"?
|
||||
members.invite_desc = Add a new member to %s:
|
||||
members.invite_now = Invite now
|
||||
|
||||
teams.join = Join
|
||||
teams.leave = Leave
|
||||
|
@ -2974,7 +2908,6 @@ teams.admin_access_helper = Members can pull and push to team repositories and a
|
|||
teams.no_desc = This team has no description
|
||||
teams.settings = Settings
|
||||
teams.owners_permission_desc = Owners have full access to <strong>all repositories</strong> and have <strong>administrator access</strong> to the organization.
|
||||
teams.members = Team members
|
||||
teams.update_settings = Update settings
|
||||
teams.delete_team = Delete team
|
||||
teams.add_team_member = Add team member
|
||||
|
@ -2984,11 +2917,7 @@ teams.delete_team_title = Delete team
|
|||
teams.delete_team_desc = Deleting a team revokes repository access from its members. Continue?
|
||||
teams.delete_team_success = The team has been deleted.
|
||||
teams.admin_permission_desc = This team grants <strong>Administrator</strong> access: members can read from, push to and add collaborators to team repositories.
|
||||
teams.create_repo_permission_desc = Additionally, this team grants <strong>Create repository</strong> permission: members can create new repositories in organization.
|
||||
teams.repositories = Team repositories
|
||||
teams.remove_all_repos_title = Remove all team repositories
|
||||
teams.remove_all_repos_desc = This will remove all repositories from the team.
|
||||
teams.add_all_repos_title = Add all repositories
|
||||
teams.add_all_repos_desc = This will add all the organization's repositories to the team.
|
||||
teams.add_nonexistent_repo = The repository you're trying to add doesn't exist, please create it first.
|
||||
teams.add_duplicate_users = User is already a team member.
|
||||
|
@ -3201,7 +3130,6 @@ repos.unadopted = Unadopted repositories
|
|||
repos.unadopted.no_more = No unadopted repositories found.
|
||||
repos.owner = Owner
|
||||
repos.name = Name
|
||||
repos.private = Private
|
||||
repos.issues = Issues
|
||||
repos.size = Size
|
||||
repos.lfs_size = LFS size
|
||||
|
@ -3240,7 +3168,6 @@ auths.updated = Updated
|
|||
auths.auth_type = Authentication type
|
||||
auths.auth_name = Authentication name
|
||||
auths.security_protocol = Security protocol
|
||||
auths.domain = Domain
|
||||
auths.host = Host
|
||||
auths.port = Port
|
||||
auths.bind_dn = Bind DN
|
||||
|
@ -3270,7 +3197,6 @@ auths.user_attribute_in_group = User attribute listed in group
|
|||
auths.map_group_to_team = Map LDAP groups to Organization teams (leave the field empty to skip)
|
||||
auths.map_group_to_team_removal = Remove users from synchronized teams if user does not belong to corresponding LDAP group
|
||||
auths.enable_ldap_groups = Enable LDAP groups
|
||||
auths.ms_ad_sa = MS AD search attributes
|
||||
auths.smtp_auth = SMTP authentication type
|
||||
auths.smtphost = SMTP host
|
||||
auths.smtpport = SMTP port
|
||||
|
@ -3336,7 +3262,6 @@ auths.delete_auth_desc = Deleting an authentication source prevents users from u
|
|||
auths.still_in_used = The authentication source is still in use. Convert or delete any users using this authentication source first.
|
||||
auths.deletion_success = The authentication source has been deleted.
|
||||
auths.login_source_exist = The authentication source "%s" already exists.
|
||||
auths.login_source_of_type_exist = An authentication source of this type already exists.
|
||||
auths.unable_to_initialize_openid = Unable to initialize OpenID Connect Provider: %s
|
||||
auths.invalid_openIdConnectAutoDiscoveryURL = Invalid Auto Discovery URL (this must be a valid URL starting with http:// or https://)
|
||||
|
||||
|
@ -3355,7 +3280,6 @@ config.run_mode = Run mode
|
|||
config.git_version = Git version
|
||||
config.app_data_path = App data path
|
||||
config.repo_root_path = Repository root path
|
||||
config.lfs_root_path = LFS root path
|
||||
config.log_file_root_path = Log path
|
||||
config.script_type = Script type
|
||||
config.reverse_auth_user = Reverse proxy authentication user
|
||||
|
@ -3433,9 +3357,6 @@ config.send_test_mail_submit = Send
|
|||
config.test_mail_failed = Failed to send a test email to "%s": %v
|
||||
config.test_mail_sent = A test email has been sent to "%s".
|
||||
|
||||
config.oauth_config = OAuth configuration
|
||||
config.oauth_enabled = Enabled
|
||||
|
||||
config.cache_config = Cache configuration
|
||||
config.cache_adapter = Cache adapter
|
||||
config.cache_interval = Cache interval
|
||||
|
@ -3454,10 +3375,8 @@ config.cookie_name = Cookie name
|
|||
config.gc_interval_time = GC interval time
|
||||
config.session_life_time = Session lifetime
|
||||
config.https_only = HTTPS only
|
||||
config.cookie_life_time = Cookie lifetime
|
||||
|
||||
config.picture_config = Picture and avatar configuration
|
||||
config.picture_service = Picture service
|
||||
config.disable_gravatar = Disable Gravatar
|
||||
config.enable_federated_avatar = Enable federated avatars
|
||||
config.open_with_editor_app_help = The "Open with" editors for the clone menu. If left empty, the default will be used. Expand to see the default.
|
||||
|
@ -3477,7 +3396,6 @@ config.git_gc_timeout = GC Operation timeout
|
|||
config.log_config = Log configuration
|
||||
config.logger_name_fmt = Logger: %s
|
||||
config.disabled_logger = Disabled
|
||||
config.access_log_mode = Access log mode
|
||||
config.access_log_template = Access log template
|
||||
config.xorm_log_sql = Log SQL
|
||||
|
||||
|
@ -3496,14 +3414,10 @@ monitor.stacktrace = Stacktrace
|
|||
monitor.processes_count = %d Processes
|
||||
monitor.download_diagnosis_report = Download diagnosis report
|
||||
monitor.duration = Duration (s)
|
||||
monitor.desc = Description
|
||||
monitor.start = Start Time
|
||||
monitor.execute_time = Execution Time
|
||||
monitor.last_execution_result = Result
|
||||
monitor.process.cancel = Cancel process
|
||||
monitor.process.cancel_desc = Canceling a process may cause data loss
|
||||
monitor.process.cancel_notices = Cancel: <strong>%s</strong>?
|
||||
monitor.process.children = Children
|
||||
|
||||
monitor.queues = Queues
|
||||
monitor.queue = Queue: %s
|
||||
|
@ -3642,11 +3556,9 @@ error.probable_bad_default_signature = WARNING! Although the default key has thi
|
|||
[units]
|
||||
unit = Unit
|
||||
error.no_unit_allowed_repo = You are not allowed to access any section of this repository.
|
||||
error.unit_not_allowed = You are not allowed to access this repository section.
|
||||
|
||||
[packages]
|
||||
title = Packages
|
||||
desc = Manage repository packages.
|
||||
empty = There are no packages yet.
|
||||
empty.documentation = For more information on the package registry, see <a target="_blank" rel="noopener noreferrer" href="%s">the documentation</a>.
|
||||
empty.repo = Did you upload a package, but it's not shown here? Go to <a href="%[1]s">package settings</a> and link it to this repo.
|
||||
|
@ -3707,7 +3619,6 @@ composer.registry = Setup this registry in your <code>~/.composer/config.json</c
|
|||
composer.install = To install the package using Composer, run the following command:
|
||||
composer.dependencies = Dependencies
|
||||
composer.dependencies.development = Development dependencies
|
||||
conan.details.repository = Repository
|
||||
conan.registry = Setup this registry from the command line:
|
||||
conan.install = To install the package using Conan, run the following command:
|
||||
conda.registry = Setup this registry as a Conda repository in your <code>.condarc</code> file:
|
||||
|
@ -3806,7 +3717,6 @@ owner.settings.cleanuprules.none = There are no cleanup rules yet.
|
|||
owner.settings.cleanuprules.preview = Cleanup rule preview
|
||||
owner.settings.cleanuprules.preview.overview = %d packages are scheduled to be removed.
|
||||
owner.settings.cleanuprules.preview.none = Cleanup rule does not match any packages.
|
||||
owner.settings.cleanuprules.enabled = Enabled
|
||||
owner.settings.cleanuprules.pattern_full_match = Apply pattern to full package name
|
||||
owner.settings.cleanuprules.keep.title = Versions that match these rules are kept, even if they match a removal rule below.
|
||||
owner.settings.cleanuprules.keep.count = Keep the most recent
|
||||
|
@ -3840,7 +3750,6 @@ management = Manage secrets
|
|||
|
||||
[actions]
|
||||
actions = Actions
|
||||
unit.desc = Manage integrated CI/CD pipelines with Forgejo Actions.
|
||||
|
||||
status.unknown = Unknown
|
||||
status.waiting = Waiting
|
||||
|
@ -3931,7 +3840,6 @@ variables.none = There are no variables yet.
|
|||
variables.deletion = Remove variable
|
||||
variables.deletion.description = Removing a variable is permanent and cannot be undone. Continue?
|
||||
variables.description = Variables will be passed to certain actions and cannot be read otherwise.
|
||||
variables.id_not_exist = Variable with ID %d does not exist.
|
||||
variables.edit = Edit Variable
|
||||
variables.not_found = Failed to find the variable.
|
||||
variables.deletion.failed = Failed to remove variable.
|
||||
|
|
|
@ -427,6 +427,7 @@ func (diffFile *DiffFile) ShouldBeHidden() bool {
|
|||
return diffFile.IsGenerated || diffFile.IsViewed
|
||||
}
|
||||
|
||||
//llu:returnsTrKey
|
||||
func (diffFile *DiffFile) ModeTranslationKey(mode string) string {
|
||||
switch mode {
|
||||
case "040000":
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue