Merge remote-tracking branch 'github/dev' into feat/1.0.7/user-avatar

This commit is contained in:
LinkinStars 2023-03-07 15:24:08 +08:00
commit f319b43412
90 changed files with 3155 additions and 2563 deletions

View File

@ -1,6 +1,6 @@
.PHONY: build clean ui
VERSION=1.0.5
VERSION=1.0.6
BIN=answer
DIR_SRC=./cmd/answer
DOCKER_CMD=docker

View File

@ -6033,9 +6033,6 @@ const docTemplate = `{
"schema.GetOtherUserInfoResp": {
"type": "object",
"properties": {
"has": {
"type": "boolean"
},
"info": {
"$ref": "#/definitions/schema.GetOtherUserInfoByUsernameResp"
}
@ -7737,7 +7734,6 @@ const docTemplate = `{
],
"properties": {
"status": {
"description": "user status",
"type": "string",
"enum": [
"normal",
@ -7747,7 +7743,6 @@ const docTemplate = `{
]
},
"user_id": {
"description": "user id",
"type": "string"
}
}
@ -8043,6 +8038,10 @@ const docTemplate = `{
"label": {
"type": "string"
},
"progress": {
"description": "Translation completion percentage",
"type": "integer"
},
"value": {
"type": "string"
}

View File

@ -6021,9 +6021,6 @@
"schema.GetOtherUserInfoResp": {
"type": "object",
"properties": {
"has": {
"type": "boolean"
},
"info": {
"$ref": "#/definitions/schema.GetOtherUserInfoByUsernameResp"
}
@ -7725,7 +7722,6 @@
],
"properties": {
"status": {
"description": "user status",
"type": "string",
"enum": [
"normal",
@ -7735,7 +7731,6 @@
]
},
"user_id": {
"description": "user id",
"type": "string"
}
}
@ -8031,6 +8026,10 @@
"label": {
"type": "string"
},
"progress": {
"description": "Translation completion percentage",
"type": "integer"
},
"value": {
"type": "string"
}

View File

@ -501,8 +501,6 @@ definitions:
type: object
schema.GetOtherUserInfoResp:
properties:
has:
type: boolean
info:
$ref: '#/definitions/schema.GetOtherUserInfoByUsernameResp'
type: object
@ -1704,7 +1702,6 @@ definitions:
schema.UpdateUserStatusReq:
properties:
status:
description: user status
enum:
- normal
- suspended
@ -1712,7 +1709,6 @@ definitions:
- inactive
type: string
user_id:
description: user id
type: string
required:
- status
@ -1924,6 +1920,9 @@ definitions:
properties:
label:
type: string
progress:
description: Translation completion percentage
type: integer
value:
type: string
type: object

13
go.mod
View File

@ -35,13 +35,13 @@ require (
github.com/segmentfault/pacman/contrib/server/http v0.0.0-20221018072427-a15dd1434e05
github.com/spf13/cobra v1.6.1
github.com/stretchr/testify v1.8.1
github.com/swaggo/files v0.0.0-20220728132757-551d4a08d97a
github.com/swaggo/files v1.0.0
github.com/swaggo/gin-swagger v1.5.3
github.com/swaggo/swag v1.8.7
github.com/swaggo/swag v1.8.10
github.com/tidwall/gjson v1.14.4
github.com/yuin/goldmark v1.4.13
golang.org/x/crypto v0.1.0
golang.org/x/net v0.1.0
golang.org/x/net v0.2.0
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df
gopkg.in/yaml.v3 v3.0.1
modernc.org/sqlite v1.14.2
@ -121,7 +121,7 @@ require (
go.uber.org/zap v1.23.0 // indirect
golang.org/x/image v0.1.0 // indirect
golang.org/x/mod v0.6.0 // indirect
golang.org/x/sys v0.1.0 // indirect
golang.org/x/sys v0.2.0 // indirect
golang.org/x/text v0.5.0 // indirect
golang.org/x/tools v0.2.0 // indirect
google.golang.org/protobuf v1.28.1 // indirect
@ -139,8 +139,3 @@ require (
modernc.org/token v1.0.0 // indirect
sigs.k8s.io/yaml v1.3.0 // indirect
)
// github action runner Sometimes it will time out.
replace gitee.com/travelliu/dm v1.8.11192 => github.com/aichy126/dm v1.8.11192
replace modernc.org/z v1.2.19 => github.com/aichy126/modernc.org_z v1.2.19

22
go.sum
View File

@ -38,6 +38,7 @@ cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3f
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
gitea.com/xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a h1:lSA0F4e9A2NcQSqGqTOXqu2aRi/XEQxDCBwM8yJtE6s=
gitea.com/xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a/go.mod h1:EXuID2Zs0pAQhH8yz+DNjUbjppKQzKFAn28TMYPB6IU=
gitee.com/travelliu/dm v1.8.11192/go.mod h1:DHTzyhCrM843x9VdKVbZ+GKXGRbKM2sJ4LxihRxShkE=
github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78 h1:w+iIsaOQNcT7OZ575w+acHgRric5iCyQh+xv+KJ4HB8=
github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
@ -63,9 +64,6 @@ github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMx
github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g=
github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c=
github.com/agiledragon/gomonkey/v2 v2.3.1/go.mod h1:ap1AmDzcVOAz1YpeJ3TCzIgstoaWLA6jbbgxfB4w2iY=
github.com/aichy126/dm v1.8.11192/go.mod h1:DHTzyhCrM843x9VdKVbZ+GKXGRbKM2sJ4LxihRxShkE=
github.com/aichy126/modernc.org_z v1.2.19 h1:g4KvQojkFWBJk47OGvKzuudTr5mRF7jORSwobYyc2Sc=
github.com/aichy126/modernc.org_z v1.2.19/go.mod h1:+ZpP0pc4zz97eukOzW3xagV/lS82IpPN9NGG5pNF9vY=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
@ -670,13 +668,14 @@ github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKs
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/subosito/gotenv v1.4.1 h1:jyEFiXpy21Wm81FBN71l9VoMMV8H8jG+qIK3GCpY6Qs=
github.com/subosito/gotenv v1.4.1/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0=
github.com/swaggo/files v0.0.0-20220728132757-551d4a08d97a h1:kAe4YSu0O0UFn1DowNo2MY5p6xzqtJ/wQ7LZynSvGaY=
github.com/swaggo/files v0.0.0-20220728132757-551d4a08d97a/go.mod h1:lKJPbtWzJ9JhsTN1k1gZgleJWY/cqq0psdoMmaThG3w=
github.com/swaggo/files v1.0.0 h1:1gGXVIeUFCS/dta17rnP0iOpr6CXFwKD7EO5ID233e4=
github.com/swaggo/files v1.0.0/go.mod h1:N59U6URJLyU1PQgFqPM7wXLMhJx7QAolnvfQkqO13kc=
github.com/swaggo/gin-swagger v1.5.3 h1:8mWmHLolIbrhJJTflsaFoZzRBYVmEE7JZGIq08EiC0Q=
github.com/swaggo/gin-swagger v1.5.3/go.mod h1:3XJKSfHjDMB5dBo/0rrTXidPmgLeqsX89Yp4uA50HpI=
github.com/swaggo/swag v1.8.1/go.mod h1:ugemnJsPZm/kRwFUnzBlbHRd0JY9zE1M4F+uy2pAaPQ=
github.com/swaggo/swag v1.8.7 h1:2K9ivTD3teEO+2fXV6zrZKDqk5IuU2aJtBDo8U7omWU=
github.com/swaggo/swag v1.8.7/go.mod h1:ezQVUUhly8dludpVk+/PuwJWvLLanB13ygV5Pr9enSk=
github.com/swaggo/swag v1.8.10 h1:eExW4bFa52WOjqRzRD58bgWsWfdFJso50lpbeTcmTfo=
github.com/swaggo/swag v1.8.10/go.mod h1:ezQVUUhly8dludpVk+/PuwJWvLLanB13ygV5Pr9enSk=
github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww=
github.com/syndtr/goleveldb v1.0.0 h1:fBdIW9lB4Iz0n9khmH8w27SJ3QEJ7+IgjPEwGSZiFdE=
github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ=
@ -852,8 +851,8 @@ golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qx
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.1.0 h1:hZ/3BUoy5aId7sCpA/Tc5lt8DkFgdVS2onTpJsZ/fl0=
golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco=
golang.org/x/net v0.2.0 h1:sZfSu1wtKLGlWI4ZZayP0ck9Y73K1ynO6gqzTdBVdPU=
golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@ -946,11 +945,12 @@ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.1.0 h1:kunALQeHf1/185U1i0GOB/fy1IPRDDpuoOOqRReG57U=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.2.0 h1:ljd4t30dBnAvMZaQCevtY0xLLD0A+bRZXbgLMLU1F/A=
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@ -1295,6 +1295,8 @@ modernc.org/tcl v1.8.13 h1:V0sTNBw0Re86PvXZxuCub3oO9WrSTqALgrwNZNvLFGw=
modernc.org/tcl v1.8.13/go.mod h1:V+q/Ef0IJaNUSECieLU4o+8IScapxnMyFV6i/7uQlAY=
modernc.org/token v1.0.0 h1:a0jaWiNMDhDUtqOj09wvjWWAqd3q7WpBulmL9H2egsk=
modernc.org/token v1.0.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
modernc.org/z v1.2.19 h1:BGyRFWhDVn5LFS5OcX4Yd/MlpRTOc7hOPTdcIpCiUao=
modernc.org/z v1.2.19/go.mod h1:+ZpP0pc4zz97eukOzW3xagV/lS82IpPN9NGG5pNF9vY=
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=

View File

@ -107,6 +107,8 @@ backend:
other: Should not contain synonym tags.
cannot_update:
other: No permission to update.
is_used_cannot_delete:
other: You cannot delete a tag that is in use
cannot_set_synonym_as_itself:
other: You cannot set the synonym of the current tag as itself.
smtp:
@ -309,7 +311,7 @@ ui:
msg:
empty: Code cannot be empty.
language:
label: Language (optional)
label: Language
placeholder: Automatic detection
btn_cancel: Cancel
btn_confirm: Add
@ -345,7 +347,7 @@ ui:
only_image: Only image files are allowed.
max_size: File size cannot exceed 4MB.
desc:
label: Description (optional)
label: Description
tab_url: Image URL
form_url:
fields:
@ -354,7 +356,7 @@ ui:
msg:
empty: Image URL cannot be empty.
name:
label: Description (optional)
label: Description
btn_cancel: Cancel
btn_confirm: Add
uploading: Uploading
@ -374,7 +376,7 @@ ui:
msg:
empty: URL cannot be empty.
name:
label: Description (optional)
label: Description
btn_cancel: Cancel
btn_confirm: Add
ordered_list:
@ -422,7 +424,7 @@ ui:
range: URL slug up to 35 characters.
character: URL slug contains unallowed character set.
desc:
label: Description (optional)
label: Description
btn_cancel: Cancel
btn_submit: Submit
tag_info:
@ -439,9 +441,11 @@ ui:
synonyms_text: The following tags will be remapped to
delete:
title: Delete this tag
content: >-
<p>We do not allow deleting tag with posts.</p><p>Please remove this tag from the posts first.</p>
content2: Are you sure you wish to delete?
tip_with_posts: >-
<p>We do not allowed <strong>deleting tag with posts</strong>.</p> <p>Please remove this tag from the posts first.</p>
tip_with_synonyms: >-
<p>We do not allowed <strong>deleting tag with synonyms</strong>.</p> <p>Please remove the synonyms from this tag first.</p>
tip: Are you sure you wish to delete?
close: Close
edit_tag:
title: Edit Tag
@ -677,13 +681,13 @@ ui:
default: System
msg: Please upload an avatar
bio:
label: About Me (optional)
label: About Me
website:
label: Website (optional)
label: Website
placeholder: "https://example.com"
msg: Website incorrect format
location:
label: Location (optional)
label: Location
placeholder: "City, Country"
notification:
heading: Notifications
@ -1163,11 +1167,11 @@ ui:
validate: Please enter a valid URL.
text: The address of your site.
short_desc:
label: Short Site Description (optional)
label: Short Site Description
msg: Short site description cannot be empty.
text: "Short description, as used in the title tag on homepage."
desc:
label: Site Description (optional)
label: Site Description
msg: Site description cannot be empty.
text: "Describe this site in one sentence, as used in the meta description tag."
contact_email:
@ -1177,14 +1181,6 @@ ui:
text: Email address of key contact responsible for this site.
interface:
page_title: Interface
logo:
label: Logo (optional)
msg: Site logo cannot be empty.
text: You can upload your image or <1>reset</1> it to the site title text.
theme:
label: Theme
msg: Theme cannot be empty.
text: Select an existing theme.
language:
label: Interface Language
msg: Interface language cannot be empty.
@ -1236,18 +1232,18 @@ ui:
branding:
page_title: Branding
logo:
label: Logo (optional)
label: Logo
msg: Logo cannot be empty.
text: The logo image at the top left of your site. Use a wide rectangular image with a height of 56 and an aspect ratio greater than 3:1. If left blank, the site title text will be shown.
mobile_logo:
label: Mobile Logo (optional)
label: Mobile Logo
text: The logo used on mobile version of your site. Use a wide rectangular image with a height of 56. If left blank, the image from the "logo" setting will be used.
square_icon:
label: Square Icon (optional)
label: Square Icon
msg: Square icon cannot be empty.
text: Image used as the base for metadata icons. Should ideally be larger than 512x512.
favicon:
label: Favicon (optional)
label: Favicon
text: A favicon for your site. To work correctly over a CDN it must be a png. Will be resized to 32x32. If left blank, "square icon" will be used.
legal:
page_title: Legal
@ -1313,6 +1309,7 @@ ui:
label: Login required
text: Only logged in users can access this community.
form:
optional: (optional)
empty: cannot be empty
invalid: is invalid
btn_submit: Save
@ -1362,5 +1359,7 @@ ui:
staffs: Our community staff
reputation: reputation
votes: votes
prompt:
leave_page: "Are you sure you want to leave the page?"
changes_not_save: "Your changes may not be saved."

View File

@ -35,6 +35,10 @@ backend:
other: Email and password do not match.
error:
admin:
cannot_update_their_password:
other: You cannot modify your password.
cannot_modify_self_status:
other: You cannot modify your status.
email_or_password_wrong:
other: Email and password do not match.
answer:
@ -81,6 +85,8 @@ backend:
new_password_same_as_previous_setting:
other: The new password is the same as the previous one.
question:
already_deleted:
other: This post has been deleted.
not_found:
other: Question not found.
cannot_deleted:
@ -111,7 +117,7 @@ backend:
cannot_update:
other: No permission to update.
is_used_cannot_delete:
other: "You cannot delete a tag that is in use"
other: You cannot delete a tag that is in use
cannot_set_synonym_as_itself:
other: You cannot set the synonym of the current tag as itself.
smtp:
@ -821,6 +827,7 @@ ui:
approve: Approve
reject: Reject
skip: Skip
discard_draft: Discard draft
search:
title: Search Results
keywords: Keywords
@ -1017,9 +1024,11 @@ ui:
answers: answers
accepted: Accepted
page_404:
http_error: HTTP Error 404
desc: "Unfortunately, this page doesn't exist."
back_home: Back to homepage
page_50X:
http_error: HTTP Error 500
desc: The server encountered an error and could not complete your request.
back_home: Back to homepage
page_maintenance:
@ -1409,6 +1418,10 @@ ui:
reputation: reputation
votes: votes
prompt:
leave_page: "Are you sure you want to leave the page?"
changes_not_save: "Your changes may not be saved."
leave_page: Are you sure you want to leave the page?
changes_not_save: Your changes may not be saved.
draft:
discard_confirm: Are you sure you want to discard your draft?
messages:
post_deleted: This post has been deleted.

View File

@ -107,6 +107,8 @@ backend:
other: No debe contener etiquetas sinónimas.
cannot_update:
other: Sin permiso para actualizar.
is_used_cannot_delete:
other: You cannot delete a tag that is in use
cannot_set_synonym_as_itself:
other: No se puede establecer como sinónimo de una etiqueta la propia etiqueta.
smtp:
@ -330,7 +332,7 @@ ui:
msg:
empty: Código no puede estar vacío.
language:
label: Lenguage (opcional)
label: Language
placeholder: Detección automática
btn_cancel: Cancelar
btn_confirm: Añadir
@ -366,7 +368,7 @@ ui:
only_image: Solo se permiten archivos de imagen.
max_size: El tamaño del archivo no puede superar 4MB.
desc:
label: Descripción (opcional)
label: Description
tab_url: URL de la imagen
form_url:
fields:
@ -375,7 +377,7 @@ ui:
msg:
empty: La URL de la imagen no puede estar vacía.
name:
label: Descripción (opcional)
label: Description
btn_cancel: Cancelar
btn_confirm: Añadir
uploading: Subiendo
@ -395,7 +397,7 @@ ui:
msg:
empty: La dirección no puede estar vacía.
name:
label: Descripción (opcional)
label: Description
btn_cancel: Cancelar
btn_confirm: Añadir
ordered_list:
@ -443,7 +445,7 @@ ui:
range: URL slug hasta 35 caracteres.
character: La URL amigable contiene caracteres no permitidos.
desc:
label: Descripción (opcional)
label: Description
btn_cancel: Cancelar
btn_submit: Enviar
tag_info:
@ -460,9 +462,11 @@ ui:
synonyms_text: Las siguientes etiquetas serán reasignadas a
delete:
title: Eliminar esta etiqueta
content: >-
<p>No se permite la eliminación de etiquetas con posts.</p><p>Por favor antes elimina esta etiqueta del post.</p>
content2: '¿Estás seguro de que deseas borrarlo?'
tip_with_posts: >-
<p>We do not allowed <strong>deleting tag with posts</strong>.</p> <p>Please remove this tag from the posts first.</p>
tip_with_synonyms: >-
<p>We do not allowed <strong>deleting tag with synonyms</strong>.</p> <p>Please remove the synonyms from this tag first.</p>
tip: Are you sure you wish to delete?
close: Cerrar
edit_tag:
title: Editar etiqueta
@ -698,13 +702,13 @@ ui:
default: Sistema
msg: Por favor, sube una imagen
bio:
label: Sobre mí (opcional)
label: About Me
website:
label: Sitio web (opcional)
label: Website
placeholder: "https://example.com"
msg: Formato del sitio web incorrecto
location:
label: Ubicación (opcional)
label: Location
placeholder: "Ciudad, País"
notification:
heading: Notificaciones
@ -1188,11 +1192,11 @@ ui:
validate: Please enter a valid URL.
text: The address of your site.
short_desc:
label: Short Site Description (optional)
label: Short Site Description
msg: Short site description cannot be empty.
text: "Short description, as used in the title tag on homepage."
desc:
label: Site Description (optional)
label: Site Description
msg: Site description cannot be empty.
text: "Describe this site in one sentence, as used in the meta description tag."
contact_email:
@ -1202,14 +1206,6 @@ ui:
text: Email address of key contact responsible for this site.
interface:
page_title: Interface
logo:
label: Logo (optional)
msg: Site logo cannot be empty.
text: You can upload your image or <1>reset</1> it to the site title text.
theme:
label: Theme
msg: Theme cannot be empty.
text: Select an existing theme.
language:
label: Interface Language
msg: Interface language cannot be empty.
@ -1261,18 +1257,18 @@ ui:
branding:
page_title: Branding
logo:
label: Logo (optional)
label: Logo
msg: Logo cannot be empty.
text: The logo image at the top left of your site. Use a wide rectangular image with a height of 56 and an aspect ratio greater than 3:1. If left blank, the site title text will be shown.
mobile_logo:
label: Mobile Logo (optional)
label: Mobile Logo
text: The logo used on mobile version of your site. Use a wide rectangular image with a height of 56. If left blank, the image from the "logo" setting will be used.
square_icon:
label: Square Icon (optional)
label: Square Icon
msg: Square icon cannot be empty.
text: Image used as the base for metadata icons. Should ideally be larger than 512x512.
favicon:
label: Favicon (optional)
label: Favicon
text: A favicon for your site. To work correctly over a CDN it must be a png. Will be resized to 32x32. If left blank, "square icon" will be used.
legal:
page_title: Legal
@ -1338,6 +1334,7 @@ ui:
label: Inicio de sesión requerido
text: Sólo usuarios con sesión iniciada pueden acceder a esta comunidad.
form:
optional: (optional)
empty: no puede estar en blanco
invalid: no es válido
btn_submit: Guardar
@ -1387,5 +1384,7 @@ ui:
staffs: Nuestor equipo de la comunidad
reputation: reputación
votes: votos
prompt:
leave_page: "Are you sure you want to leave the page?"
changes_not_save: "Your changes may not be saved."

View File

@ -107,6 +107,8 @@ backend:
other: Ne dois pas contenir de tags synonymes.
cannot_update:
other: Pas de permission pour mettre à jour.
is_used_cannot_delete:
other: Vous ne pouvez pas supprimer un tag utilisé
cannot_set_synonym_as_itself:
other: Vous ne pouvez pas définir le synonyme de la balise actuelle comme elle-même.
smtp:
@ -309,7 +311,7 @@ ui:
msg:
empty: Le code ne peut pas être vide.
language:
label: Langue (facultatif)
label: Langage
placeholder: Détection automatique
btn_cancel: Annuler
btn_confirm: Ajouter
@ -345,7 +347,7 @@ ui:
only_image: Seules les images sont autorisées.
max_size: La taille du fichier ne doit pas dépasser 4 Mo.
desc:
label: Description (optionnel)
label: Description
tab_url: URL de l'image
form_url:
fields:
@ -354,7 +356,7 @@ ui:
msg:
empty: L'URL de l'image ne peut pas être vide.
name:
label: Description (optionnel)
label: Description
btn_cancel: Annuler
btn_confirm: Ajouter
uploading: Téléversement en cours
@ -374,7 +376,7 @@ ui:
msg:
empty: L'URL ne peut pas être vide.
name:
label: Description (facultatif)
label: Description
btn_cancel: Annuler
btn_confirm: Ajouter
ordered_list:
@ -422,7 +424,7 @@ ui:
range: Titre de 35 caractères maximum.
character: Le slug d'URL contient un jeu de caractères non autorisé.
desc:
label: Description (facultatif)
label: Description
btn_cancel: Annuler
btn_submit: Valider
tag_info:
@ -439,9 +441,11 @@ ui:
synonyms_text: Les balises suivantes seront remappées en
delete:
title: Supprimer cette étiquette
content: >-
<p>Nous ne permettons pas de supprimer le tag avec les posts.</p><p>Veuillez d'abord supprimer ce tag des posts.</p>
content2: Etes-vous sûr de vouloir supprimer ?
tip_with_posts: >-
<p>Nous ne permettons pas de <strong>supprimer un tag avec des posts.</strong></p><p>Veuillez d'abord supprimer ce tag des posts.</p>
tip_with_synonyms: >-
<p>Nous ne permettons pas de <strong>supprimer un tag avec des synonymes</strong>.</p> <p>Veuillez d'abord supprimer les synonymes de ce tag.</p>
tip: Êtes-vous sûr de vouloir supprimer ?
close: Fermer
edit_tag:
title: Editer le tag
@ -677,13 +681,13 @@ ui:
default: Système
msg: Veuillez charger un avatar
bio:
label: À propos de moi (optionnel)
label: À propos de moi
website:
label: Site web (facultatif)
label: Site Web
placeholder: "https://example.com"
msg: Format du site web incorrect
location:
label: Emplacement (facultatif)
label: Position
placeholder: "Ville, Pays"
notification:
heading: Notifications
@ -1163,11 +1167,11 @@ ui:
validate: Indiquez une URL valide.
text: L'adresse de ce site.
short_desc:
label: Description courte (optionnel)
label: Description Courte
msg: La description courte ne peut pas être vide.
text: "La description courte, telle qu'elle est utilisée dans le tag titre de la page d'accueil."
desc:
label: Description du site (optionnel)
label: Description du Site
msg: La description du site ne peut pas être vide.
text: "Décrivez ce site en une phrase, telle qu'elle est utilisée dans la balise meta description."
contact_email:
@ -1177,14 +1181,6 @@ ui:
text: L'adresse email du responsable du site.
interface:
page_title: Interface
logo:
label: Logo (optionnel)
msg: Le logo ne peut pas être vide.
text: Vous pouvez télécharger votre image ou la <1>réinitialiser</1> sur le texte du titre du site.
theme:
label: Thème
msg: Le thème ne peut pas être vide.
text: Sélectionne un thème existant.
language:
label: Langue de l'interface
msg: La langue de l'interface ne peut pas être vide.
@ -1236,18 +1232,18 @@ ui:
branding:
page_title: Marque
logo:
label: Logo (optionnel)
label: Logo
msg: Le logo ne peut pas être vide.
text: L'image du logo en haut à gauche de votre site. Utilisez une grande image rectangulaire avec une hauteur de 56 et un ratio d'aspect supérieur à 3:1. Si laissé vide, le titre du site sera affiché.
mobile_logo:
label: Logo mobile (optionnel)
label: Logo Mobile
text: Le logo utilisé sur la version mobile de votre site. Utilisez une image rectangulaire large avec une hauteur de 56. Si laissé vide, l'image du paramètre « logo » sera utilisée.
square_icon:
label: Icône carrée (optionnel)
label: Icône carrée
msg: L'icône carrée ne peut pas être vide.
text: Image utilisée comme base pour les icônes de métadonnées. Idéalement supérieure à 512x512.
favicon:
label: Favicon (optionnel)
label: Favicon
text: Une favicon pour votre site. Pour fonctionner correctement sur un CDN, il doit s'agir d'un png. Sera redimensionné en 32x32. Si laissé vide, « icône carrée » sera utilisé.
legal:
page_title: Légal
@ -1313,6 +1309,7 @@ ui:
label: Connexion requise
text: Seuls les utilisateurs connectés peuvent accéder à cette communauté.
form:
optional: (optionnel)
empty: ne peut pas être vide
invalid: est invalide
btn_submit: Sauvegarder
@ -1362,5 +1359,7 @@ ui:
staffs: Staff de la communauté
reputation: réputation
votes: votes
prompt:
leave_page: "Voulez-vous vraiment quitter la page ?"
changes_not_save: "Impossible d'enregistrer vos modifications."

View File

@ -1,26 +1,41 @@
# all support language
language_options:
- label: "English(US)"
- label: "English"
value: "en_US"
- label: "Español(ES)"
progress: 100
- label: "Español"
value: "es_ES"
- label: "Português(PT)"
progress: 0
- label: "Português(BR)"
value: "pt_BR"
progress: 0
- label: "Português"
value: "pt_PT"
- label: "Deutsch(DE)"
progress: 0
- label: "Deutsch"
value: "de_DE"
- label: "Français(FR)"
progress: 4
- label: "Français"
value: "fr_FR"
- label: "日本語(JA)"
progress: 100
- label: "日本語"
value: "ja_JP"
- label: "Italiano(IT)"
progress: 0
- label: "Italiano"
value: "it_IT"
- label: "Русский(RU)"
progress: 16
- label: "Русский"
value: "ru_RU"
- label: "简体中文(CN)"
progress: 13
- label: "简体中文"
value: "zh_CN"
- label: "繁體中文(CN)"
progress: 100
- label: "繁體中文"
value: "zh_TW"
- label: "한국어(KO)"
progress: 100
- label: "한국어"
value: "ko_KR"
- label: "Tiếng Việt(VI)"
progress: 0
- label: "Tiếng Việt"
value: "vi_VN"
progress: 0

View File

@ -107,6 +107,8 @@ backend:
other: Tidak boleh mengandung Tag sinonim.
cannot_update:
other: Tidak memiliki izin untuk memperbaharui.
is_used_cannot_delete:
other: You cannot delete a tag that is in use
cannot_set_synonym_as_itself:
other: Anda tidak bisa menetapkan sinonim dari tag saat ini dengan tag yang sama.
smtp:
@ -309,7 +311,7 @@ ui:
msg:
empty: Code tidak boleh kosong.
language:
label: Bahasa (opsional)
label: Language
placeholder: Deteksi otomatis
btn_cancel: Batal
btn_confirm: Tambah
@ -345,7 +347,7 @@ ui:
only_image: Hanya file Gambar yang diperbolehkan.
max_size: Ukuran file tidak boleh melebihi 4MB.
desc:
label: Deskripsi (opsional)
label: Description
tab_url: URL gambar
form_url:
fields:
@ -354,7 +356,7 @@ ui:
msg:
empty: URL gambar tidak boleh kosong.
name:
label: Deskripsi (opsional)
label: Description
btn_cancel: Batal
btn_confirm: Tambah
uploading: Sedang mengunggah
@ -374,7 +376,7 @@ ui:
msg:
empty: URL cannot be empty.
name:
label: Description (optional)
label: Description
btn_cancel: Batal
btn_confirm: Tambah
ordered_list:
@ -422,7 +424,7 @@ ui:
range: URL slug up to 35 characters.
character: URL slug contains unallowed character set.
desc:
label: Description (optional)
label: Description
btn_cancel: Cancel
btn_submit: Submit
tag_info:
@ -439,9 +441,11 @@ ui:
synonyms_text: Tag berikut akan dipetakan ulang ke
delete:
title: Hapus tagar ini
content: >-
<p>We do not allow deleting tag with posts.</p><p>Please remove this tag from the posts first.</p>
content2: Anda yakin ingin menghapusnya?
tip_with_posts: >-
<p>We do not allowed <strong>deleting tag with posts</strong>.</p> <p>Please remove this tag from the posts first.</p>
tip_with_synonyms: >-
<p>We do not allowed <strong>deleting tag with synonyms</strong>.</p> <p>Please remove the synonyms from this tag first.</p>
tip: Are you sure you wish to delete?
close: Tutup
edit_tag:
title: Ubah Tag
@ -677,13 +681,13 @@ ui:
default: System
msg: Please upload an avatar
bio:
label: About Me (optional)
label: About Me
website:
label: Website (optional)
label: Website
placeholder: "https://example.com"
msg: Website incorrect format
location:
label: Location (optional)
label: Location
placeholder: "City, Country"
notification:
heading: Notifications
@ -1163,11 +1167,11 @@ ui:
validate: Please enter a valid URL.
text: The address of your site.
short_desc:
label: Short Site Description (optional)
label: Short Site Description
msg: Short site description cannot be empty.
text: "Short description, as used in the title tag on homepage."
desc:
label: Site Description (optional)
label: Site Description
msg: Site description cannot be empty.
text: "Describe this site in one sentence, as used in the meta description tag."
contact_email:
@ -1177,14 +1181,6 @@ ui:
text: Email address of key contact responsible for this site.
interface:
page_title: Interface
logo:
label: Logo (optional)
msg: Site logo cannot be empty.
text: You can upload your image or <1>reset</1> it to the site title text.
theme:
label: Theme
msg: Theme cannot be empty.
text: Select an existing theme.
language:
label: Interface Language
msg: Interface language cannot be empty.
@ -1236,18 +1232,18 @@ ui:
branding:
page_title: Branding
logo:
label: Logo (optional)
label: Logo
msg: Logo cannot be empty.
text: The logo image at the top left of your site. Use a wide rectangular image with a height of 56 and an aspect ratio greater than 3:1. If left blank, the site title text will be shown.
mobile_logo:
label: Mobile Logo (optional)
label: Mobile Logo
text: The logo used on mobile version of your site. Use a wide rectangular image with a height of 56. If left blank, the image from the "logo" setting will be used.
square_icon:
label: Square Icon (optional)
label: Square Icon
msg: Square icon cannot be empty.
text: Image used as the base for metadata icons. Should ideally be larger than 512x512.
favicon:
label: Favicon (optional)
label: Favicon
text: A favicon for your site. To work correctly over a CDN it must be a png. Will be resized to 32x32. If left blank, "square icon" will be used.
legal:
page_title: Legal
@ -1313,6 +1309,7 @@ ui:
label: Login required
text: Only logged in users can access this community.
form:
optional: (optional)
empty: cannot be empty
invalid: is invalid
btn_submit: Save
@ -1362,5 +1359,7 @@ ui:
staffs: Our community staff
reputation: reputation
votes: votes
prompt:
leave_page: "Are you sure you want to leave the page?"
changes_not_save: "Your changes may not be saved."

View File

@ -107,6 +107,8 @@ backend:
other: Non deve contenere tag sinonimi.
cannot_update:
other: Nessun permesso per l'aggiornamento.
is_used_cannot_delete:
other: You cannot delete a tag that is in use
cannot_set_synonym_as_itself:
other: Non puoi impostare il sinonimo del tag corrente come se stesso.
smtp:
@ -309,7 +311,7 @@ ui:
msg:
empty: Code cannot be empty.
language:
label: Language (optional)
label: Language
placeholder: Automatic detection
btn_cancel: Cancel
btn_confirm: Add
@ -345,7 +347,7 @@ ui:
only_image: Only image files are allowed.
max_size: File size cannot exceed 4MB.
desc:
label: Description (optional)
label: Description
tab_url: Image URL
form_url:
fields:
@ -354,7 +356,7 @@ ui:
msg:
empty: Image URL cannot be empty.
name:
label: Description (optional)
label: Description
btn_cancel: Cancel
btn_confirm: Add
uploading: Uploading
@ -374,7 +376,7 @@ ui:
msg:
empty: URL cannot be empty.
name:
label: Description (optional)
label: Description
btn_cancel: Cancel
btn_confirm: Add
ordered_list:
@ -422,7 +424,7 @@ ui:
range: URL slug up to 35 characters.
character: URL slug contains unallowed character set.
desc:
label: Description (optional)
label: Description
btn_cancel: Cancel
btn_submit: Submit
tag_info:
@ -439,9 +441,11 @@ ui:
synonyms_text: The following tags will be remapped to
delete:
title: Delete this tag
content: >-
<p>We do not allow deleting tag with posts.</p><p>Please remove this tag from the posts first.</p>
content2: Are you sure you wish to delete?
tip_with_posts: >-
<p>We do not allowed <strong>deleting tag with posts</strong>.</p> <p>Please remove this tag from the posts first.</p>
tip_with_synonyms: >-
<p>We do not allowed <strong>deleting tag with synonyms</strong>.</p> <p>Please remove the synonyms from this tag first.</p>
tip: Are you sure you wish to delete?
close: Close
edit_tag:
title: Edit Tag
@ -677,13 +681,13 @@ ui:
default: System
msg: Please upload an avatar
bio:
label: About Me (optional)
label: About Me
website:
label: Website (optional)
label: Website
placeholder: "https://example.com"
msg: Website incorrect format
location:
label: Location (optional)
label: Location
placeholder: "City, Country"
notification:
heading: Notifications
@ -1163,11 +1167,11 @@ ui:
validate: Please enter a valid URL.
text: The address of your site.
short_desc:
label: Short Site Description (optional)
label: Short Site Description
msg: Short site description cannot be empty.
text: "Short description, as used in the title tag on homepage."
desc:
label: Site Description (optional)
label: Site Description
msg: Site description cannot be empty.
text: "Describe this site in one sentence, as used in the meta description tag."
contact_email:
@ -1177,14 +1181,6 @@ ui:
text: Email address of key contact responsible for this site.
interface:
page_title: Interface
logo:
label: Logo (optional)
msg: Site logo cannot be empty.
text: You can upload your image or <1>reset</1> it to the site title text.
theme:
label: Theme
msg: Theme cannot be empty.
text: Select an existing theme.
language:
label: Interface Language
msg: Interface language cannot be empty.
@ -1236,18 +1232,18 @@ ui:
branding:
page_title: Branding
logo:
label: Logo (optional)
label: Logo
msg: Logo cannot be empty.
text: The logo image at the top left of your site. Use a wide rectangular image with a height of 56 and an aspect ratio greater than 3:1. If left blank, the site title text will be shown.
mobile_logo:
label: Mobile Logo (optional)
label: Mobile Logo
text: The logo used on mobile version of your site. Use a wide rectangular image with a height of 56. If left blank, the image from the "logo" setting will be used.
square_icon:
label: Square Icon (optional)
label: Square Icon
msg: Square icon cannot be empty.
text: Image used as the base for metadata icons. Should ideally be larger than 512x512.
favicon:
label: Favicon (optional)
label: Favicon
text: A favicon for your site. To work correctly over a CDN it must be a png. Will be resized to 32x32. If left blank, "square icon" will be used.
legal:
page_title: Legal
@ -1313,6 +1309,7 @@ ui:
label: Login required
text: Only logged in users can access this community.
form:
optional: (optional)
empty: cannot be empty
invalid: is invalid
btn_submit: Save
@ -1362,5 +1359,7 @@ ui:
staffs: Our community staff
reputation: reputation
votes: votes
prompt:
leave_page: "Are you sure you want to leave the page?"
changes_not_save: "Your changes may not be saved."

View File

@ -107,6 +107,8 @@ backend:
other: Should not contain synonym tags.
cannot_update:
other: No permission to update.
is_used_cannot_delete:
other: You cannot delete a tag that is in use
cannot_set_synonym_as_itself:
other: You cannot set the synonym of the current tag as itself.
smtp:
@ -309,7 +311,7 @@ ui:
msg:
empty: Code cannot be empty.
language:
label: Language (optional)
label: Language
placeholder: Automatic detection
btn_cancel: Cancel
btn_confirm: Add
@ -345,7 +347,7 @@ ui:
only_image: Only image files are allowed.
max_size: File size cannot exceed 4MB.
desc:
label: Description (optional)
label: Description
tab_url: Image URL
form_url:
fields:
@ -354,7 +356,7 @@ ui:
msg:
empty: Image URL cannot be empty.
name:
label: Description (optional)
label: Description
btn_cancel: Cancel
btn_confirm: Add
uploading: Uploading
@ -374,7 +376,7 @@ ui:
msg:
empty: URL cannot be empty.
name:
label: Description (optional)
label: Description
btn_cancel: Cancel
btn_confirm: Add
ordered_list:
@ -422,7 +424,7 @@ ui:
range: URL slug up to 35 characters.
character: URL slug contains unallowed character set.
desc:
label: Description (optional)
label: Description
btn_cancel: Cancel
btn_submit: Submit
tag_info:
@ -439,9 +441,11 @@ ui:
synonyms_text: The following tags will be remapped to
delete:
title: Delete this tag
content: >-
<p>We do not allow deleting tag with posts.</p><p>Please remove this tag from the posts first.</p>
content2: Are you sure you wish to delete?
tip_with_posts: >-
<p>We do not allowed <strong>deleting tag with posts</strong>.</p> <p>Please remove this tag from the posts first.</p>
tip_with_synonyms: >-
<p>We do not allowed <strong>deleting tag with synonyms</strong>.</p> <p>Please remove the synonyms from this tag first.</p>
tip: Are you sure you wish to delete?
close: Close
edit_tag:
title: Edit Tag
@ -677,13 +681,13 @@ ui:
default: System
msg: Please upload an avatar
bio:
label: About Me (optional)
label: About Me
website:
label: Website (optional)
label: Website
placeholder: "https://example.com"
msg: Website incorrect format
location:
label: Location (optional)
label: Location
placeholder: "City, Country"
notification:
heading: Notifications
@ -1163,11 +1167,11 @@ ui:
validate: Please enter a valid URL.
text: The address of your site.
short_desc:
label: Short Site Description (optional)
label: Short Site Description
msg: Short site description cannot be empty.
text: "Short description, as used in the title tag on homepage."
desc:
label: Site Description (optional)
label: Site Description
msg: Site description cannot be empty.
text: "Describe this site in one sentence, as used in the meta description tag."
contact_email:
@ -1177,14 +1181,6 @@ ui:
text: Email address of key contact responsible for this site.
interface:
page_title: Interface
logo:
label: Logo (optional)
msg: Site logo cannot be empty.
text: You can upload your image or <1>reset</1> it to the site title text.
theme:
label: Theme
msg: Theme cannot be empty.
text: Select an existing theme.
language:
label: Interface Language
msg: Interface language cannot be empty.
@ -1236,18 +1232,18 @@ ui:
branding:
page_title: Branding
logo:
label: Logo (optional)
label: Logo
msg: Logo cannot be empty.
text: The logo image at the top left of your site. Use a wide rectangular image with a height of 56 and an aspect ratio greater than 3:1. If left blank, the site title text will be shown.
mobile_logo:
label: Mobile Logo (optional)
label: Mobile Logo
text: The logo used on mobile version of your site. Use a wide rectangular image with a height of 56. If left blank, the image from the "logo" setting will be used.
square_icon:
label: Square Icon (optional)
label: Square Icon
msg: Square icon cannot be empty.
text: Image used as the base for metadata icons. Should ideally be larger than 512x512.
favicon:
label: Favicon (optional)
label: Favicon
text: A favicon for your site. To work correctly over a CDN it must be a png. Will be resized to 32x32. If left blank, "square icon" will be used.
legal:
page_title: Legal
@ -1313,6 +1309,7 @@ ui:
label: Login required
text: Only logged in users can access this community.
form:
optional: (optional)
empty: cannot be empty
invalid: is invalid
btn_submit: Save
@ -1362,5 +1359,7 @@ ui:
staffs: Our community staff
reputation: reputation
votes: votes
prompt:
leave_page: "Are you sure you want to leave the page?"
changes_not_save: "Your changes may not be saved."

View File

@ -107,6 +107,8 @@ backend:
other: Should not contain synonym tags.
cannot_update:
other: No permission to update.
is_used_cannot_delete:
other: You cannot delete a tag that is in use
cannot_set_synonym_as_itself:
other: You cannot set the synonym of the current tag as itself.
smtp:
@ -309,7 +311,7 @@ ui:
msg:
empty: Code cannot be empty.
language:
label: Language (optional)
label: Language
placeholder: Automatic detection
btn_cancel: Cancel
btn_confirm: Add
@ -345,7 +347,7 @@ ui:
only_image: Only image files are allowed.
max_size: File size cannot exceed 4MB.
desc:
label: Description (optional)
label: Description
tab_url: Image URL
form_url:
fields:
@ -354,7 +356,7 @@ ui:
msg:
empty: Image URL cannot be empty.
name:
label: Description (optional)
label: Description
btn_cancel: Cancel
btn_confirm: Add
uploading: Uploading
@ -374,7 +376,7 @@ ui:
msg:
empty: URL cannot be empty.
name:
label: Description (optional)
label: Description
btn_cancel: Cancel
btn_confirm: Add
ordered_list:
@ -422,7 +424,7 @@ ui:
range: URL slug up to 35 characters.
character: URL slug contains unallowed character set.
desc:
label: Description (optional)
label: Description
btn_cancel: Cancel
btn_submit: Submit
tag_info:
@ -439,9 +441,11 @@ ui:
synonyms_text: The following tags will be remapped to
delete:
title: Delete this tag
content: >-
<p>We do not allow deleting tag with posts.</p><p>Please remove this tag from the posts first.</p>
content2: Are you sure you wish to delete?
tip_with_posts: >-
<p>We do not allowed <strong>deleting tag with posts</strong>.</p> <p>Please remove this tag from the posts first.</p>
tip_with_synonyms: >-
<p>We do not allowed <strong>deleting tag with synonyms</strong>.</p> <p>Please remove the synonyms from this tag first.</p>
tip: Are you sure you wish to delete?
close: Close
edit_tag:
title: Edit Tag
@ -677,13 +681,13 @@ ui:
default: System
msg: Please upload an avatar
bio:
label: About Me (optional)
label: About Me
website:
label: Website (optional)
label: Website
placeholder: "https://example.com"
msg: Website incorrect format
location:
label: Location (optional)
label: Location
placeholder: "City, Country"
notification:
heading: Notifications
@ -1163,11 +1167,11 @@ ui:
validate: Please enter a valid URL.
text: The address of your site.
short_desc:
label: Short Site Description (optional)
label: Short Site Description
msg: Short site description cannot be empty.
text: "Short description, as used in the title tag on homepage."
desc:
label: Site Description (optional)
label: Site Description
msg: Site description cannot be empty.
text: "Describe this site in one sentence, as used in the meta description tag."
contact_email:
@ -1177,14 +1181,6 @@ ui:
text: Email address of key contact responsible for this site.
interface:
page_title: Interface
logo:
label: Logo (optional)
msg: Site logo cannot be empty.
text: You can upload your image or <1>reset</1> it to the site title text.
theme:
label: Theme
msg: Theme cannot be empty.
text: Select an existing theme.
language:
label: Interface Language
msg: Interface language cannot be empty.
@ -1236,18 +1232,18 @@ ui:
branding:
page_title: Branding
logo:
label: Logo (optional)
label: Logo
msg: Logo cannot be empty.
text: The logo image at the top left of your site. Use a wide rectangular image with a height of 56 and an aspect ratio greater than 3:1. If left blank, the site title text will be shown.
mobile_logo:
label: Mobile Logo (optional)
label: Mobile Logo
text: The logo used on mobile version of your site. Use a wide rectangular image with a height of 56. If left blank, the image from the "logo" setting will be used.
square_icon:
label: Square Icon (optional)
label: Square Icon
msg: Square icon cannot be empty.
text: Image used as the base for metadata icons. Should ideally be larger than 512x512.
favicon:
label: Favicon (optional)
label: Favicon
text: A favicon for your site. To work correctly over a CDN it must be a png. Will be resized to 32x32. If left blank, "square icon" will be used.
legal:
page_title: Legal
@ -1313,6 +1309,7 @@ ui:
label: Login required
text: Only logged in users can access this community.
form:
optional: (optional)
empty: cannot be empty
invalid: is invalid
btn_submit: Save
@ -1362,5 +1359,7 @@ ui:
staffs: Our community staff
reputation: reputation
votes: votes
prompt:
leave_page: "Are you sure you want to leave the page?"
changes_not_save: "Your changes may not be saved."

File diff suppressed because it is too large Load Diff

View File

@ -14,153 +14,155 @@ backend:
role:
name:
user:
other: User
other: Usuário
admin:
other: Admin
other: Administrador
moderator:
other: Moderator
other: Moderador
description:
user:
other: Default with no special access.
other: Padrão sem acesso especial.
admin:
other: Have the full power to access the site.
other: Possui acesso total ao site.
moderator:
other: Has access to all posts except admin settings.
other: Possui acesso a todas as postagens exceto às configurações de usuários.
email:
other: Email
other: E-mail
password:
other: Password
other: Senha
email_or_password_wrong_error:
other: Email and password do not match.
other: O e-mail e a palavra-passe não coincidem.
error:
admin:
email_or_password_wrong:
other: Email and password do not match.
other: O e-mail e a palavra-passe não coincidem.
answer:
not_found:
other: Answer do not found.
other: Resposta não encontrada.
cannot_deleted:
other: No permission to delete.
other: Sem permissão para remover.
cannot_update:
other: No permission to update.
other: Sem permissão para atualizar.
comment:
edit_without_permission:
other: Comment are not allowed to edit.
other: Não é possível alterar comentários.
not_found:
other: Comment not found.
other: Comentário não encontrado.
cannot_edit_after_deadline:
other: The comment time has been too long to modify.
other: O tempo do comentário foi muito longo para ser modificado.
email:
duplicate:
other: Email already exists.
other: O e-mail já existe.
need_to_be_verified:
other: Email should be verified.
other: O e-mail deve ser verificado.
verify_url_expired:
other: Email verified URL has expired, please resend the email.
other: O e-mail verificado URL expirou, por favor, reenvie o e-mail.
lang:
not_found:
other: Language file not found.
other: Arquivo de idioma não encontrado.
object:
captcha_verification_failed:
other: Captcha wrong.
other: O Captcha está incorreto.
disallow_follow:
other: You are not allowed to follow.
other: Você não possui autorização suficiente para seguir.
disallow_vote:
other: You are not allowed to vote.
other: Você não possui permissão para votar.
disallow_vote_your_self:
other: You can't vote for your own post.
other: Você não pode votar na sua própria postagem.
not_found:
other: Object not found.
other: Objeto não encontrado.
verification_failed:
other: Verification failed.
other: A verificação falhou.
email_or_password_incorrect:
other: Email and password do not match.
other: O e-mail e a senha não correspondem.
old_password_verification_failed:
other: The old password verification failed
other: Falha na verificação de senha antiga
new_password_same_as_previous_setting:
other: The new password is the same as the previous one.
other: A nova senha é a mesma que a anterior.
question:
not_found:
other: Question not found.
other: Pergunta não encontrada.
cannot_deleted:
other: No permission to delete.
other: Sem permissão para remover.
cannot_close:
other: No permission to close.
other: Sem permissão para fechar.
cannot_update:
other: No permission to update.
other: Sem permissão para atualizar.
rank:
fail_to_meet_the_condition:
other: Rank fail to meet the condition.
other: O nível não consegue satisfazer a condição.
report:
handle_failed:
other: Report handle failed.
other: Falha ao manusear relatório.
not_found:
other: Report not found.
other: Relatório não encontrado.
tag:
not_found:
other: Tag not found.
other: Marcador não encontrado.
recommend_tag_not_found:
other: Recommend Tag is not exist.
other: O marcador recomendado não existe.
recommend_tag_enter:
other: Please enter at least one required tag.
other: Por favor, insira pelo menos um marcador obrigatório.
not_contain_synonym_tags:
other: Should not contain synonym tags.
other: Não deve conter marcadores sinónimos.
cannot_update:
other: No permission to update.
other: Sem permissão para atualizar.
is_used_cannot_delete:
other: Não é possível excluir um marcador em uso
cannot_set_synonym_as_itself:
other: You cannot set the synonym of the current tag as itself.
other: Você não pode definir o sinônimo do marcador atual como a si mesmo.
smtp:
config_from_name_cannot_be_email:
other: The From Name cannot be a email address.
other: Nome do remetente não pode ser um endereço de e-mail.
theme:
not_found:
other: Theme not found.
other: Tema não encontrado.
revision:
review_underway:
other: Can't edit currently, there is a version in the review queue.
other: Não é possível neste momento, há uma versão na fila de análise.
no_permission:
other: No permission to Revision.
other: Sem permissão para realizar Revisão.
user:
email_or_password_wrong:
other:
other: Email and password do not match.
other: O e-mail e a senha não conferem.
not_found:
other: User not found.
other: Usuário não encontrado.
suspended:
other: User has been suspended.
other: O usuário foi suspenso.
username_invalid:
other: Username is invalid.
other: Nome de usuário inválido.
username_duplicate:
other: Username is already in use.
other: O nome de usuário já em uso.
set_avatar:
other: Avatar set failed.
other: Configuração de avatar falhou.
cannot_update_your_role:
other: You cannot modify your role.
other: Você não pode modificar a sua função.
not_allowed_registration:
other: Currently the site is not open for registration
other: O site não está aberto para novos registros
config:
read_config_failed:
other: Read config failed
other: Falha ao ler configuração
database:
connection_failed:
other: Database connection failed
other: Falha ao conectar-se ao banco de dados
create_table_failed:
other: Create table failed
other: Falha ao criar tabela
install:
create_config_failed:
other: Can't create the config.yaml file.
other: Não foi possível criar o arquivo de configuração.
upload:
unsupported_file_format:
other: Unsupported file format.
other: Formato de arquivo não suportado.
report:
spam:
name:
other: spam
desc:
other: This post is an advertisement, or vandalism. It is not useful or relevant to the current topic.
other: Essa postagem é um anúncio, ou vandalismo. Não é útil ou relevante para o tópico atual.
rude:
name:
other: rude or abusive
other: rude ou abusivo
desc:
other: A reasonable person would find this content inappropriate for respectful discourse.
duplicate:
@ -262,73 +264,73 @@ ui:
settings: Settings
notifications: Notifications
login: Log In
sign_up: Sign Up
account_recovery: Account Recovery
account_activation: Account Activation
confirm_email: Confirm Email
account_suspended: Account Suspended
admin: Admin
change_email: Modify Email
install: Answer Installation
upgrade: Answer Upgrade
maintenance: Website Maintenance
users: Users
sign_up: Registar-se
account_recovery: Recuperação de conta
account_activation: Ativação de conta
confirm_email: Confirmar E-mail
account_suspended: Conta suspensa
admin: Administrador
change_email: Modificar e-mail
install: Instalação do Answer
upgrade: Atualização do Answer
maintenance: Manutenção do website
users: Usuários
notifications:
title: Notifications
inbox: Inbox
achievement: Achievements
all_read: Mark all as read
show_more: Show more
title: Notificações
inbox: Caixa de entrada
achievement: Conquistas
all_read: Marcar todos como lida
show_more: Mostrar mais
suspended:
title: Your Account has been Suspended
until_time: "Your account was suspended until {{ time }}."
forever: This user was suspended forever.
end: You don't meet a community guideline.
title: A sua conta foi suspensa
until_time: "Sua conta está suspensa até {{ time }}."
forever: Este usuário foi suspenso permanentemente.
end: Você não atende a uma diretriz da comunidade.
editor:
blockquote:
text: Blockquote
text: Bloco de citação
bold:
text: Strong
text: Negrito
chart:
text: Chart
flow_chart: Flow chart
sequence_diagram: Sequence diagram
class_diagram: Class diagram
state_diagram: State diagram
entity_relationship_diagram: Entity relationship diagram
user_defined_diagram: User defined diagram
gantt_chart: Gantt chart
pie_chart: Pie chart
text: Gráfico
flow_chart: Gráfico de fluxo
sequence_diagram: Diagrama de sequência
class_diagram: Diagrama de classe
state_diagram: Diagrama de estado
entity_relationship_diagram: Diagrama de relacionamento de entidade
user_defined_diagram: Diagrama definido pelo usuário
gantt_chart: Gráfico de Gantt
pie_chart: Gráfico de pizza
code:
text: Code Sample
add_code: Add code sample
text: Exemplo de código
add_code: Adicionar exemplo de código
form:
fields:
code:
label: Code
label: Código
msg:
empty: Code cannot be empty.
empty: Código não pode ser vazio.
language:
label: Language (optional)
placeholder: Automatic detection
btn_cancel: Cancel
btn_confirm: Add
label: Idioma
placeholder: Deteção automática
btn_cancel: Cancelar
btn_confirm: Adicionar
formula:
text: Formula
text: Fórmula
options:
inline: Inline formula
block: Block formula
inline: Fórmula na linha
block: Bloco de fórmula
heading:
text: Heading
text: Cabeçalho
options:
h1: Heading 1
h2: Heading 2
h3: Heading 3
h4: Heading 4
h5: Heading 5
h6: Heading 6
h1: Cabeçalho 1
h2: Cabeçalho 2
h3: Cabeçalho 3
h4: Cabeçalho 4
h5: Cabeçalho 5
h6: Cabeçalho 6
help:
text: Help
text: Ajuda
hr:
text: Horizontal Rule
image:
@ -345,7 +347,7 @@ ui:
only_image: Only image files are allowed.
max_size: File size cannot exceed 4MB.
desc:
label: Description (optional)
label: Description
tab_url: Image URL
form_url:
fields:
@ -354,7 +356,7 @@ ui:
msg:
empty: Image URL cannot be empty.
name:
label: Description (optional)
label: Description
btn_cancel: Cancel
btn_confirm: Add
uploading: Uploading
@ -374,7 +376,7 @@ ui:
msg:
empty: URL cannot be empty.
name:
label: Description (optional)
label: Description
btn_cancel: Cancel
btn_confirm: Add
ordered_list:
@ -422,7 +424,7 @@ ui:
range: URL slug up to 35 characters.
character: URL slug contains unallowed character set.
desc:
label: Description (optional)
label: Description
btn_cancel: Cancel
btn_submit: Submit
tag_info:
@ -439,9 +441,11 @@ ui:
synonyms_text: The following tags will be remapped to
delete:
title: Delete this tag
content: >-
<p>We do not allow deleting tag with posts.</p><p>Please remove this tag from the posts first.</p>
content2: Are you sure you wish to delete?
tip_with_posts: >-
<p>We do not allowed <strong>deleting tag with posts</strong>.</p> <p>Please remove this tag from the posts first.</p>
tip_with_synonyms: >-
<p>We do not allowed <strong>deleting tag with synonyms</strong>.</p> <p>Please remove the synonyms from this tag first.</p>
tip: Are you sure you wish to delete?
close: Close
edit_tag:
title: Edit Tag
@ -677,13 +681,13 @@ ui:
default: System
msg: Please upload an avatar
bio:
label: About Me (optional)
label: About Me
website:
label: Website (optional)
label: Website
placeholder: "https://example.com"
msg: Website incorrect format
location:
label: Location (optional)
label: Location
placeholder: "City, Country"
notification:
heading: Notifications
@ -1163,11 +1167,11 @@ ui:
validate: Please enter a valid URL.
text: The address of your site.
short_desc:
label: Short Site Description (optional)
label: Short Site Description
msg: Short site description cannot be empty.
text: "Short description, as used in the title tag on homepage."
desc:
label: Site Description (optional)
label: Site Description
msg: Site description cannot be empty.
text: "Describe this site in one sentence, as used in the meta description tag."
contact_email:
@ -1177,14 +1181,6 @@ ui:
text: Email address of key contact responsible for this site.
interface:
page_title: Interface
logo:
label: Logo (optional)
msg: Site logo cannot be empty.
text: You can upload your image or <1>reset</1> it to the site title text.
theme:
label: Theme
msg: Theme cannot be empty.
text: Select an existing theme.
language:
label: Interface Language
msg: Interface language cannot be empty.
@ -1236,18 +1232,18 @@ ui:
branding:
page_title: Branding
logo:
label: Logo (optional)
label: Logo
msg: Logo cannot be empty.
text: The logo image at the top left of your site. Use a wide rectangular image with a height of 56 and an aspect ratio greater than 3:1. If left blank, the site title text will be shown.
mobile_logo:
label: Mobile Logo (optional)
label: Mobile Logo
text: The logo used on mobile version of your site. Use a wide rectangular image with a height of 56. If left blank, the image from the "logo" setting will be used.
square_icon:
label: Square Icon (optional)
label: Square Icon
msg: Square icon cannot be empty.
text: Image used as the base for metadata icons. Should ideally be larger than 512x512.
favicon:
label: Favicon (optional)
label: Favicon
text: A favicon for your site. To work correctly over a CDN it must be a png. Will be resized to 32x32. If left blank, "square icon" will be used.
legal:
page_title: Legal
@ -1313,6 +1309,7 @@ ui:
label: Login required
text: Only logged in users can access this community.
form:
optional: (optional)
empty: cannot be empty
invalid: is invalid
btn_submit: Save
@ -1362,5 +1359,7 @@ ui:
staffs: Our community staff
reputation: reputation
votes: votes
prompt:
leave_page: "Are you sure you want to leave the page?"
changes_not_save: "Your changes may not be saved."

View File

@ -107,6 +107,8 @@ backend:
other: Не должно содержать теги синонимы.
cannot_update:
other: Нет прав для обновления.
is_used_cannot_delete:
other: You cannot delete a tag that is in use
cannot_set_synonym_as_itself:
other: Вы не можете установить синоним текущего тега.
smtp:
@ -309,7 +311,7 @@ ui:
msg:
empty: Код не может быть пустым.
language:
label: Language (optional)
label: Language
placeholder: Automatic detection
btn_cancel: Cancel
btn_confirm: Add
@ -345,7 +347,7 @@ ui:
only_image: Only image files are allowed.
max_size: File size cannot exceed 4MB.
desc:
label: Description (optional)
label: Description
tab_url: Image URL
form_url:
fields:
@ -354,7 +356,7 @@ ui:
msg:
empty: Image URL cannot be empty.
name:
label: Description (optional)
label: Description
btn_cancel: Cancel
btn_confirm: Add
uploading: Uploading
@ -374,7 +376,7 @@ ui:
msg:
empty: URL cannot be empty.
name:
label: Description (optional)
label: Description
btn_cancel: Cancel
btn_confirm: Add
ordered_list:
@ -422,7 +424,7 @@ ui:
range: URL slug до 35 символов.
character: URL slug содержит недопустимый набор символов.
desc:
label: Описание (опционально)
label: Description
btn_cancel: Отмена
btn_submit: Отправить
tag_info:
@ -439,9 +441,11 @@ ui:
synonyms_text: Следующие теги будут переназначены на
delete:
title: Удалить этот тег
content: >-
<p>Мы не разрешаем удалять тег с сообщениями.</p><p>Пожалуйста, сначала удалите этот тег из сообщений</p>
content2: Вы действительно хотите удалить?
tip_with_posts: >-
<p>We do not allowed <strong>deleting tag with posts</strong>.</p> <p>Please remove this tag from the posts first.</p>
tip_with_synonyms: >-
<p>We do not allowed <strong>deleting tag with synonyms</strong>.</p> <p>Please remove the synonyms from this tag first.</p>
tip: Are you sure you wish to delete?
close: Закрыть
edit_tag:
title: Изменить тег
@ -677,13 +681,13 @@ ui:
default: System
msg: Please upload an avatar
bio:
label: About Me (optional)
label: About Me
website:
label: Website (optional)
label: Website
placeholder: "https://example.com"
msg: Website incorrect format
location:
label: Location (optional)
label: Location
placeholder: "City, Country"
notification:
heading: Notifications
@ -1163,11 +1167,11 @@ ui:
validate: Please enter a valid URL.
text: The address of your site.
short_desc:
label: Short Site Description (optional)
label: Short Site Description
msg: Short site description cannot be empty.
text: "Short description, as used in the title tag on homepage."
desc:
label: Site Description (optional)
label: Site Description
msg: Site description cannot be empty.
text: "Describe this site in one sentence, as used in the meta description tag."
contact_email:
@ -1177,14 +1181,6 @@ ui:
text: Email address of key contact responsible for this site.
interface:
page_title: Interface
logo:
label: Logo (optional)
msg: Site logo cannot be empty.
text: You can upload your image or <1>reset</1> it to the site title text.
theme:
label: Theme
msg: Theme cannot be empty.
text: Select an existing theme.
language:
label: Interface Language
msg: Interface language cannot be empty.
@ -1236,18 +1232,18 @@ ui:
branding:
page_title: Branding
logo:
label: Logo (optional)
label: Logo
msg: Logo cannot be empty.
text: The logo image at the top left of your site. Use a wide rectangular image with a height of 56 and an aspect ratio greater than 3:1. If left blank, the site title text will be shown.
mobile_logo:
label: Mobile Logo (optional)
label: Mobile Logo
text: The logo used on mobile version of your site. Use a wide rectangular image with a height of 56. If left blank, the image from the "logo" setting will be used.
square_icon:
label: Square Icon (optional)
label: Square Icon
msg: Square icon cannot be empty.
text: Image used as the base for metadata icons. Should ideally be larger than 512x512.
favicon:
label: Favicon (optional)
label: Favicon
text: A favicon for your site. To work correctly over a CDN it must be a png. Will be resized to 32x32. If left blank, "square icon" will be used.
legal:
page_title: Legal
@ -1313,6 +1309,7 @@ ui:
label: Login required
text: Only logged in users can access this community.
form:
optional: (optional)
empty: cannot be empty
invalid: is invalid
btn_submit: Save
@ -1362,5 +1359,7 @@ ui:
staffs: Our community staff
reputation: reputation
votes: votes
prompt:
leave_page: "Are you sure you want to leave the page?"
changes_not_save: "Your changes may not be saved."

View File

@ -2,23 +2,23 @@
backend:
base:
success:
other: Success.
other: Başarılı.
unknown:
other: Unknown error.
other: Bilinmeyen hata.
request_format_error:
other: Request format is not valid.
unauthorized_error:
other: Unauthorized.
other: İzin yok.
database_error:
other: Data server error.
other: Veri sunucu hatası.
role:
name:
user:
other: User
other: Kullanıcı
admin:
other: Admin
other: Yönetici
moderator:
other: Moderator
other: Moderatör
description:
user:
other: Default with no special access.
@ -27,131 +27,133 @@ backend:
moderator:
other: Has access to all posts except admin settings.
email:
other: Email
other: E-Posta
password:
other: Password
other: Parola
email_or_password_wrong_error:
other: Email and password do not match.
other: E-Posta ve parola eşleşmiyor.
error:
admin:
email_or_password_wrong:
other: Email and password do not match.
other: E-Posta ve parola eşleşmiyor.
answer:
not_found:
other: Answer do not found.
other: Cevap bulunamadı.
cannot_deleted:
other: No permission to delete.
other: Silme izni yok.
cannot_update:
other: No permission to update.
other: Düzenleme izni yok.
comment:
edit_without_permission:
other: Comment are not allowed to edit.
other: Yorum düzenleme izni yok.
not_found:
other: Comment not found.
other: Yorum bulunamadı.
cannot_edit_after_deadline:
other: The comment time has been too long to modify.
email:
duplicate:
other: Email already exists.
other: Bu e-posta adresi kullanılmaktadır.
need_to_be_verified:
other: Email should be verified.
other: E-Posta doğrulanmalı.
verify_url_expired:
other: Email verified URL has expired, please resend the email.
lang:
not_found:
other: Language file not found.
other: Dil dosyası bulunamadı.
object:
captcha_verification_failed:
other: Captcha wrong.
other: Captcha yanlış.
disallow_follow:
other: You are not allowed to follow.
other: Takip etme izniniz yok.
disallow_vote:
other: You are not allowed to vote.
other: Oy verme yetkiniz yok.
disallow_vote_your_self:
other: You can't vote for your own post.
other: Kendi mesajınız için oy kullanamazsınız.
not_found:
other: Object not found.
other: Nesne bulunamadı.
verification_failed:
other: Verification failed.
other: Doğrulama başarısız.
email_or_password_incorrect:
other: Email and password do not match.
other: E-Posta ve parola eşleşmiyor.
old_password_verification_failed:
other: The old password verification failed
other: Eski parola doğrulaması başarısız
new_password_same_as_previous_setting:
other: The new password is the same as the previous one.
other: Yeni parola bir önceki parolanızın aynısı.
question:
not_found:
other: Question not found.
other: Soru bulunamadı.
cannot_deleted:
other: No permission to delete.
other: Silme izni yok.
cannot_close:
other: No permission to close.
other: Kapatma izni yok.
cannot_update:
other: No permission to update.
other: Düzenleme izni yok.
rank:
fail_to_meet_the_condition:
other: Rank fail to meet the condition.
report:
handle_failed:
other: Report handle failed.
other: Rapor işlenemedi.
not_found:
other: Report not found.
other: Rapor bulunamadı.
tag:
not_found:
other: Tag not found.
other: Etiket bulunamadı.
recommend_tag_not_found:
other: Recommend Tag is not exist.
other: Önerilen Etiket yok.
recommend_tag_enter:
other: Please enter at least one required tag.
other: Lütfen en az bir adet gerekli etiket giriniz.
not_contain_synonym_tags:
other: Should not contain synonym tags.
cannot_update:
other: No permission to update.
other: Düzenleme izni yok.
is_used_cannot_delete:
other: You cannot delete a tag that is in use
cannot_set_synonym_as_itself:
other: You cannot set the synonym of the current tag as itself.
smtp:
config_from_name_cannot_be_email:
other: The From Name cannot be a email address.
other: Gönderen adı bir e-posta adresi olamaz.
theme:
not_found:
other: Theme not found.
other: Tema bulunamadı.
revision:
review_underway:
other: Can't edit currently, there is a version in the review queue.
no_permission:
other: No permission to Revision.
other: Revizyon izni yok.
user:
email_or_password_wrong:
other:
other: Email and password do not match.
other: E-Posta ve parola eşleşmiyor.
not_found:
other: User not found.
other: Kullanıcı bulunamadı.
suspended:
other: User has been suspended.
other: Kullanıcı askıya alındı.
username_invalid:
other: Username is invalid.
other: Kullanıcı adı geçersiz.
username_duplicate:
other: Username is already in use.
other: Kullanıcı adı zaten kullanımda.
set_avatar:
other: Avatar set failed.
other: Avatar ayarlama başarısız.
cannot_update_your_role:
other: You cannot modify your role.
other: Kendi rolünüzü değiştiremezsiniz.
not_allowed_registration:
other: Currently the site is not open for registration
other: Site şu anda kayıt olmaya açık değil
config:
read_config_failed:
other: Read config failed
database:
connection_failed:
other: Database connection failed
other: Veri tabanı bağlantısı başarısız
create_table_failed:
other: Create table failed
other: Tablo oluşturulamadı
install:
create_config_failed:
other: Can't create the config.yaml file.
other: Config.yaml dosyası oluşturulamadı.
upload:
unsupported_file_format:
other: Unsupported file format.
other: Desteklenmeyen dosya formatı.
report:
spam:
name:
@ -170,7 +172,7 @@ backend:
other: This question has been asked before and already has an answer.
not_answer:
name:
other: not an answer
other: cevap değil
desc:
other: This was posted as an answer, but it does not attempt to answer the question. It should possibly be an edit, a comment, another question, or deleted altogether.
not_need:
@ -229,15 +231,15 @@ backend:
reply_to_you:
other: replied to you
mention_you:
other: mentioned you
other: sizden bahsetti
your_question_is_closed:
other: Your question has been closed
other: Sorunuz kapatıldı
your_question_was_deleted:
other: Your question has been deleted
other: Sorunuz silindi
your_answer_was_deleted:
other: Your answer has been deleted
other: Cevabınız silindi
your_comment_was_deleted:
other: Your comment has been deleted
other: Yorumunuz silindi
#The following fields are used for interface presentation(Front-end)
ui:
how_to_format:
@ -245,53 +247,53 @@ ui:
desc: >-
<ul class="mb-0"><li><p class="mb-2">to make links</p><pre class="mb-2"><code>&lt;https://url.com&gt;<br/><br/>[Title](https://url.com)</code></pre></li><li><p class="mb-2">put returns between paragraphs</p></li><li><p class="mb-2"><em>_italic_</em> or **<strong>bold</strong>**</p></li><li><p class="mb-2">indent code by 4 spaces</p></li><li><p class="mb-2">quote by placing <code>&gt;</code> at start of line</p></li><li><p class="mb-2">backtick escapes <code>`like _this_`</code></p></li><li><p class="mb-2">create code fences with backticks <code>`</code></p><pre class="mb-0"><code>```<br/>code here<br/>```</code></pre></li></ul>
pagination:
prev: Prev
next: Next
prev: Önceki
next: Sonraki
page_title:
question: Question
questions: Questions
tag: Tag
tags: Tags
question: Soru
questions: Sorular
tag: Etiket
tags: Etiketler
tag_wiki: tag wiki
edit_tag: Edit Tag
ask_a_question: Add Question
edit_question: Edit Question
edit_answer: Edit Answer
search: Search
edit_tag: Etiketi Düzenle
ask_a_question: Soru Ekle
edit_question: Soruyu Düzenle
edit_answer: Cevabı Düzenle
search: Ara
posts_containing: Posts containing
settings: Settings
notifications: Notifications
login: Log In
sign_up: Sign Up
account_recovery: Account Recovery
account_activation: Account Activation
confirm_email: Confirm Email
account_suspended: Account Suspended
admin: Admin
change_email: Modify Email
settings: Ayarlar
notifications: Bildirimler
login: Oturum Aç
sign_up: Üye Ol
account_recovery: Hesap Kurtarma
account_activation: Hesap Aktivasyonu
confirm_email: E-Posta Adresinizi Onaylayın
account_suspended: Hesap Askıya Alındı
admin: Yönetici
change_email: E-Postayı değiştir
install: Answer Installation
upgrade: Answer Upgrade
maintenance: Website Maintenance
users: Users
maintenance: Website Bakımı
users: Kullanıcı
notifications:
title: Notifications
inbox: Inbox
achievement: Achievements
all_read: Mark all as read
show_more: Show more
title: Bildirimler
inbox: Gelen Kutusu
achievement: Başarılar
all_read: Tümünü okundu olarak işaretle
show_more: Daha fazla göster
suspended:
title: Your Account has been Suspended
title: Hesabınız Askıya Alındı
until_time: "Your account was suspended until {{ time }}."
forever: This user was suspended forever.
end: You don't meet a community guideline.
editor:
blockquote:
text: Blockquote
text: Alıntı
bold:
text: Strong
chart:
text: Chart
flow_chart: Flow chart
flow_chart: Akış Şeması
sequence_diagram: Sequence diagram
class_diagram: Class diagram
state_diagram: State diagram
@ -309,7 +311,7 @@ ui:
msg:
empty: Code cannot be empty.
language:
label: Language (optional)
label: Language
placeholder: Automatic detection
btn_cancel: Cancel
btn_confirm: Add
@ -345,7 +347,7 @@ ui:
only_image: Only image files are allowed.
max_size: File size cannot exceed 4MB.
desc:
label: Description (optional)
label: Description
tab_url: Image URL
form_url:
fields:
@ -354,10 +356,10 @@ ui:
msg:
empty: Image URL cannot be empty.
name:
label: Description (optional)
btn_cancel: Cancel
btn_confirm: Add
uploading: Uploading
label: Description
btn_cancel: İptal
btn_confirm: Ekle
uploading: Gönderiliyor
indent:
text: Indent
outdent:
@ -365,54 +367,54 @@ ui:
italic:
text: Emphasis
link:
text: Hyperlink
add_link: Add hyperlink
text: Bağlantı
add_link: Bağlantı ekle
form:
fields:
url:
label: URL
msg:
empty: URL cannot be empty.
empty: URL boş olamaz.
name:
label: Description (optional)
btn_cancel: Cancel
btn_confirm: Add
label: Description
btn_cancel: İptal Et
btn_confirm: Ekle
ordered_list:
text: Numbered List
text: Numaralı liste
unordered_list:
text: Bulleted List
table:
text: Table
heading: Heading
cell: Cell
text: Tablo
heading: Başlık
cell: Hücre
close_modal:
title: I am closing this post as...
btn_cancel: Cancel
btn_submit: Submit
btn_cancel: İptal Et
btn_submit: Gönder
remark:
empty: Cannot be empty.
empty: Boş olamaz.
msg:
empty: Please select a reason.
empty: Lütfen bir sebep seçin.
report_modal:
flag_title: I am flagging to report this post as...
close_title: I am closing this post as...
review_question_title: Review question
review_answer_title: Review answer
review_comment_title: Review comment
btn_cancel: Cancel
btn_submit: Submit
btn_cancel: İptal Et
btn_submit: Gönder
remark:
empty: Cannot be empty.
empty: Boş olamaz.
msg:
empty: Please select a reason.
empty: Lütfen bir sebep seçin.
tag_modal:
title: Create new tag
title: Yeni etiket oluştur
form:
fields:
display_name:
label: Display Name
label: Görüntülenen Ad
msg:
empty: Display name cannot be empty.
empty: Görünen ad boş olamaz.
range: Display name up to 35 characters.
slug_name:
label: URL Slug
@ -422,15 +424,15 @@ ui:
range: URL slug up to 35 characters.
character: URL slug contains unallowed character set.
desc:
label: Description (optional)
btn_cancel: Cancel
btn_submit: Submit
label: Description
btn_cancel: İptal Et
btn_submit: Gönder
tag_info:
created_at: Created
edited_at: Edited
history: History
created_at: Oluşturuldu
edited_at: Düzenlendi
history: Geçmiş
synonyms:
title: Synonyms
title: Eş anlamlılar
text: The following tags will be remapped to
empty: No synonyms found.
btn_add: Add a synonym
@ -438,30 +440,32 @@ ui:
btn_save: Save
synonyms_text: The following tags will be remapped to
delete:
title: Delete this tag
content: >-
<p>We do not allow deleting tag with posts.</p><p>Please remove this tag from the posts first.</p>
content2: Are you sure you wish to delete?
close: Close
title: Bu etiketi sil
tip_with_posts: >-
<p>We do not allowed <strong>deleting tag with posts</strong>.</p> <p>Please remove this tag from the posts first.</p>
tip_with_synonyms: >-
<p>We do not allowed <strong>deleting tag with synonyms</strong>.</p> <p>Please remove the synonyms from this tag first.</p>
tip: Are you sure you wish to delete?
close: Kapat
edit_tag:
title: Edit Tag
default_reason: Edit tag
title: Etiketi Düzenle
default_reason: Etiketi düzenle
form:
fields:
revision:
label: Revision
label: Revizyon
display_name:
label: Display Name
label: Görüntülenen Ad
slug_name:
label: URL Slug
info: 'Must use the character set "a-z", "0-9", "+ # - ."'
desc:
label: Description
label: ıklama
edit_summary:
label: Edit Summary
label: Özeti Düzenle
placeholder: >-
Briefly explain your changes (corrected spelling, fixed grammar, improved formatting)
btn_save_edits: Save edits
btn_save_edits: Düzenlemeleri kaydet
btn_cancel: Cancel
dates:
long_date: MMM D
@ -677,13 +681,13 @@ ui:
default: System
msg: Please upload an avatar
bio:
label: About Me (optional)
label: About Me
website:
label: Website (optional)
label: Website
placeholder: "https://example.com"
msg: Website incorrect format
location:
label: Location (optional)
label: Location
placeholder: "City, Country"
notification:
heading: Notifications
@ -1163,11 +1167,11 @@ ui:
validate: Please enter a valid URL.
text: The address of your site.
short_desc:
label: Short Site Description (optional)
label: Short Site Description
msg: Short site description cannot be empty.
text: "Short description, as used in the title tag on homepage."
desc:
label: Site Description (optional)
label: Site Description
msg: Site description cannot be empty.
text: "Describe this site in one sentence, as used in the meta description tag."
contact_email:
@ -1177,14 +1181,6 @@ ui:
text: Email address of key contact responsible for this site.
interface:
page_title: Interface
logo:
label: Logo (optional)
msg: Site logo cannot be empty.
text: You can upload your image or <1>reset</1> it to the site title text.
theme:
label: Theme
msg: Theme cannot be empty.
text: Select an existing theme.
language:
label: Interface Language
msg: Interface language cannot be empty.
@ -1236,18 +1232,18 @@ ui:
branding:
page_title: Branding
logo:
label: Logo (optional)
label: Logo
msg: Logo cannot be empty.
text: The logo image at the top left of your site. Use a wide rectangular image with a height of 56 and an aspect ratio greater than 3:1. If left blank, the site title text will be shown.
mobile_logo:
label: Mobile Logo (optional)
label: Mobile Logo
text: The logo used on mobile version of your site. Use a wide rectangular image with a height of 56. If left blank, the image from the "logo" setting will be used.
square_icon:
label: Square Icon (optional)
label: Square Icon
msg: Square icon cannot be empty.
text: Image used as the base for metadata icons. Should ideally be larger than 512x512.
favicon:
label: Favicon (optional)
label: Favicon
text: A favicon for your site. To work correctly over a CDN it must be a png. Will be resized to 32x32. If left blank, "square icon" will be used.
legal:
page_title: Legal
@ -1313,6 +1309,7 @@ ui:
label: Login required
text: Only logged in users can access this community.
form:
optional: (optional)
empty: cannot be empty
invalid: is invalid
btn_submit: Save
@ -1362,5 +1359,7 @@ ui:
staffs: Our community staff
reputation: reputation
votes: votes
prompt:
leave_page: "Are you sure you want to leave the page?"
changes_not_save: "Your changes may not be saved."

View File

@ -107,6 +107,8 @@ backend:
other: Should not contain synonym tags.
cannot_update:
other: No permission to update.
is_used_cannot_delete:
other: You cannot delete a tag that is in use
cannot_set_synonym_as_itself:
other: You cannot set the synonym of the current tag as itself.
smtp:
@ -309,7 +311,7 @@ ui:
msg:
empty: Code cannot be empty.
language:
label: Language (optional)
label: Language
placeholder: Automatic detection
btn_cancel: Cancel
btn_confirm: Add
@ -345,7 +347,7 @@ ui:
only_image: Only image files are allowed.
max_size: File size cannot exceed 4MB.
desc:
label: Description (optional)
label: Description
tab_url: Image URL
form_url:
fields:
@ -354,7 +356,7 @@ ui:
msg:
empty: Image URL cannot be empty.
name:
label: Description (optional)
label: Description
btn_cancel: Cancel
btn_confirm: Add
uploading: Uploading
@ -374,7 +376,7 @@ ui:
msg:
empty: URL cannot be empty.
name:
label: Description (optional)
label: Description
btn_cancel: Cancel
btn_confirm: Add
ordered_list:
@ -422,7 +424,7 @@ ui:
range: URL slug up to 35 characters.
character: URL slug contains unallowed character set.
desc:
label: Description (optional)
label: Description
btn_cancel: Cancel
btn_submit: Submit
tag_info:
@ -439,9 +441,11 @@ ui:
synonyms_text: The following tags will be remapped to
delete:
title: Delete this tag
content: >-
<p>We do not allow deleting tag with posts.</p><p>Please remove this tag from the posts first.</p>
content2: Are you sure you wish to delete?
tip_with_posts: >-
<p>We do not allowed <strong>deleting tag with posts</strong>.</p> <p>Please remove this tag from the posts first.</p>
tip_with_synonyms: >-
<p>We do not allowed <strong>deleting tag with synonyms</strong>.</p> <p>Please remove the synonyms from this tag first.</p>
tip: Are you sure you wish to delete?
close: Close
edit_tag:
title: Edit Tag
@ -677,13 +681,13 @@ ui:
default: System
msg: Please upload an avatar
bio:
label: About Me (optional)
label: About Me
website:
label: Website (optional)
label: Website
placeholder: "https://example.com"
msg: Website incorrect format
location:
label: Location (optional)
label: Location
placeholder: "City, Country"
notification:
heading: Notifications
@ -1163,11 +1167,11 @@ ui:
validate: Please enter a valid URL.
text: The address of your site.
short_desc:
label: Short Site Description (optional)
label: Short Site Description
msg: Short site description cannot be empty.
text: "Short description, as used in the title tag on homepage."
desc:
label: Site Description (optional)
label: Site Description
msg: Site description cannot be empty.
text: "Describe this site in one sentence, as used in the meta description tag."
contact_email:
@ -1177,14 +1181,6 @@ ui:
text: Email address of key contact responsible for this site.
interface:
page_title: Interface
logo:
label: Logo (optional)
msg: Site logo cannot be empty.
text: You can upload your image or <1>reset</1> it to the site title text.
theme:
label: Theme
msg: Theme cannot be empty.
text: Select an existing theme.
language:
label: Interface Language
msg: Interface language cannot be empty.
@ -1236,18 +1232,18 @@ ui:
branding:
page_title: Branding
logo:
label: Logo (optional)
label: Logo
msg: Logo cannot be empty.
text: The logo image at the top left of your site. Use a wide rectangular image with a height of 56 and an aspect ratio greater than 3:1. If left blank, the site title text will be shown.
mobile_logo:
label: Mobile Logo (optional)
label: Mobile Logo
text: The logo used on mobile version of your site. Use a wide rectangular image with a height of 56. If left blank, the image from the "logo" setting will be used.
square_icon:
label: Square Icon (optional)
label: Square Icon
msg: Square icon cannot be empty.
text: Image used as the base for metadata icons. Should ideally be larger than 512x512.
favicon:
label: Favicon (optional)
label: Favicon
text: A favicon for your site. To work correctly over a CDN it must be a png. Will be resized to 32x32. If left blank, "square icon" will be used.
legal:
page_title: Legal
@ -1313,6 +1309,7 @@ ui:
label: Login required
text: Only logged in users can access this community.
form:
optional: (optional)
empty: cannot be empty
invalid: is invalid
btn_submit: Save
@ -1362,5 +1359,7 @@ ui:
staffs: Our community staff
reputation: reputation
votes: votes
prompt:
leave_page: "Are you sure you want to leave the page?"
changes_not_save: "Your changes may not be saved."

View File

@ -1,4 +1,5 @@
#The following fields are used for back-end
backend:
base:
success:
@ -49,7 +50,7 @@ backend:
not_found:
other: 评论未找到。
cannot_edit_after_deadline:
other: The comment time has been too long to modify.
other: 评论时间太长,无法修改。
email:
duplicate:
other: 邮箱已经存在。
@ -80,6 +81,8 @@ backend:
new_password_same_as_previous_setting:
other: 新密码与之前的设置相同
question:
already_deleted:
other: 该内容已被删除
not_found:
other: 问题未找到
cannot_deleted:
@ -108,7 +111,7 @@ backend:
cannot_update:
other: 没有更新标签权限。
is_used_cannot_delete:
other: 您不能删除正在使用的标签
other: 您不能删除正在使用的标签
cannot_set_synonym_as_itself:
other: 您不能将当前标签的同义词设置为本身。
smtp:
@ -150,10 +153,10 @@ backend:
other: 创建表失败
install:
create_config_failed:
other: Can't create the config.yaml file.
other: 无法创建 config.yaml 文件。
upload:
unsupported_file_format:
other: Unsupported file format.
other: 不支持的文件格式。
report:
spam:
name:
@ -311,7 +314,7 @@ ui:
msg:
empty: 代码块不能为空
language:
label: 语言 (可选)
label: 语言
placeholder: 自动识别
btn_cancel: 取消
btn_confirm: 添加
@ -347,7 +350,7 @@ ui:
only_image: 只能上传图片文件。
max_size: 图片文件大小不能超过 4 MB。
desc:
label: 描述(可选)
label: 描述
tab_url: 网络图片
form_url:
fields:
@ -356,7 +359,7 @@ ui:
msg:
empty: 图片地址不能为空
name:
label: 图片描述(可选)
label: 描述
btn_cancel: 取消
btn_confirm: 添加
uploading: 上传中...
@ -376,7 +379,7 @@ ui:
msg:
empty: 链接不能为空。
name:
label: 链接描述(可选)
label: 描述
btn_cancel: 取消
btn_confirm: 添加
ordered_list:
@ -424,7 +427,7 @@ ui:
range: URL 固定链接不能超过 35 个字符。
character: URL 固定链接包含非法字符。
desc:
label: 描述(可选)
label: 描述
btn_cancel: 取消
btn_submit: 提交
tag_info:
@ -441,9 +444,11 @@ ui:
synonyms_text: 以下标签等同于
delete:
title: 删除标签
content: >-
<p>不允许删除有关联问题的标签。</p><p>请先从关联的问题中删除此标签的引用。</p>
content2: 确定要删除吗?
tip_with_posts: >-
<p>我们不允许 <strong>删除带有同义词的标签</strong>。</p> <p>请先从此标签中删除同义词。</p>
tip_with_synonyms: >-
<p>我们不允许 <strong>删除带有同义词的标签</strong>。</p> <p>请先从此标签中删除同义词。</p>
tip: 确定要删除吗?
close: 关闭
edit_tag:
title: 编辑标签
@ -484,7 +489,7 @@ ui:
btn_flag: 举报
btn_save_edits: 保存
btn_cancel: 取消
show_more: Show more comments
show_more: 显示更多评论
tip_question: >-
使用评论提问更多信息或者提出改进意见。尽量避免使用评论功能回答问题。
tip_answer: >-
@ -499,7 +504,7 @@ ui:
answer:
label: 回答内容
feedback:
characters: content must be at least 6 characters in length.
characters: 内容长度至少 6 个字符
edit_summary:
label: 编辑概要
placeholder: >-
@ -624,7 +629,7 @@ ui:
msg:
empty: 邮箱不能为空
change_email:
page_title: Welcome to {{site_name}}
page_title: 欢迎来到 {{site_name}}
btn_cancel: 取消
btn_update: 更新电子邮件地址
send_success: >-
@ -662,12 +667,12 @@ ui:
display_name:
label: 昵称
msg: 昵称不能为空。
msg_range: Display name up to 30 characters.
msg_range: 显示名称不能超过 30 个字符。
username:
label: 用户名
caption: 用户之间可以通过 "@用户名" 进行交互。
msg: 用户名不能为空
msg_range: Username up to 30 characters.
msg_range: 用户名不能超过 30 个字符。
character: '用户名只能由 "a-z", "0-9", " - . _" 组成'
avatar:
label: 头像
@ -679,13 +684,13 @@ ui:
default: 系统
msg: 请上传头像
bio:
label: 关于我 (可选)
label: 关于我
website:
label: 网站 (可选)
label: 网站
placeholder: "https://example.com"
msg: 格式不正确
location:
label: 位置 (可选)
label: 位置
placeholder: "城市, 国家"
notification:
heading: 通知
@ -753,7 +758,7 @@ ui:
confirm_info: >-
<p>您确定要提交一个新的回答吗?</p><p>您可以直接编辑和改善您之前的回答的。</p>
empty: 回答内容不能为空。
characters: content must be at least 6 characters in length.
characters: 内容长度至少 6 个字符
reopen:
title: 重新打开这个帖子
content: 确定要重新打开吗?
@ -814,7 +819,7 @@ ui:
modal_confirm:
title: 发生错误...
account_result:
page_title: Welcome to {{site_name}}
page_title: 欢迎来到 {{site_name}}
success: 你的账号已通过验证,即将返回首页。
link: 返回首页
invalid: >-
@ -825,7 +830,7 @@ ui:
unsubscribe:
page_title: 退订
success_title: 取消订阅成功
success_desc: You have been successfully removed from this subscriber list and won't receive any further emails from us.
success_desc: 您已成功地从此订阅者列表中移除,并且将不会再收到我们的任何电子邮件。
link: 更改设置
question:
following_tags: 已关注的标签
@ -887,7 +892,7 @@ ui:
title: Answer
next: 下一步
done: 完成
config_yaml_error: Can't create the config.yaml file.
config_yaml_error: 无法创建 config.yaml 文件。
lang:
label: 请选择一种语言
db_type:
@ -917,7 +922,7 @@ ui:
label: 已创建 config.yaml 文件。
desc: >-
您可以手动在 <1>/var/wwww/xxx/</1> 目录中创建<1>config.yaml</1> 文件并粘贴以下文本。
info: After you've done that, click "Next" button.
info: 完成后,点击“下一步”按钮。
site_information: 站点信息
admin_account: 管理员账户
site_name:
@ -962,12 +967,12 @@ ui:
您似乎已经安装过了。要重新安装,请先清除旧的数据库表。
db_failed: 数据连接异常!
db_failed_desc: >-
This either means that the database information in your <1>config.yaml</1> file is incorrect or that contact with the database server could not be established. This could mean your hosts database server is down.
这或者意味着数据库信息在 <1>config.yaml</1> 文件不正确,或者无法与数据库服务器建立联系。这可能意味着您的主机数据库服务器已关闭。
counts:
views: views
votes: votes
answers: answers
accepted: Accepted
views: 次浏览
votes: 个点赞
answers: 个回答
accepted: 已被采纳
page_404:
desc: "很抱歉,此页面不存在。"
back_home: 回到主页
@ -975,7 +980,7 @@ ui:
desc: 服务器遇到了一个错误,无法完成你的请求。
back_home: 回到主页
page_maintenance:
desc: "We are under maintenance, we'll be back soon."
desc: "我们正在进行维护,我们将很快回来。"
nav_menus:
dashboard: 后台管理
contents: 内容管理
@ -1109,7 +1114,7 @@ ui:
password:
label: 密码
text: 用户将被注销,需要再次登录。
msg: Password must be at 8-32 characters in length.
msg: 密码的长度必须是8-32个字符。
btn_cancel: 取消
btn_submit: 提交
user_modal:
@ -1118,13 +1123,13 @@ ui:
fields:
display_name:
label: 昵称
msg: Display Name must be at 3-30 characters in length.
msg: 显示名称长度必须为 3-30 个字符
email:
label: 邮箱
msg: 电子邮箱无效。
password:
label: 密码
msg: Password must be at 8-32 characters in length.
msg: 密码的长度必须是8-32个字符。
btn_cancel: 取消
btn_submit: 提交
questions:
@ -1165,11 +1170,11 @@ ui:
validate: 请输入一个有效的 URL。
text: 此网站的地址。
short_desc:
label: 简短站描述(可选)
label: 简短描述
msg: 简短网站描述不能为空。
text: "简短的标语作为网站主页的标题Html 的 title 标签)。"
desc:
label: 站描述 (可选)
label: 描述
msg: 网站描述不能为空。
text: "使用一句话描述本站作为网站的描述Html 的 meta 标签)。"
contact_email:
@ -1179,14 +1184,6 @@ ui:
text: 负责本网站的主要联系人的电子邮件地址。
interface:
page_title: 界面
logo:
label: Logo (可选)
msg: 不能为空
text: 可以上传图片,或者<1>重置</1>为站点标题。
theme:
label: 主题
msg: 不能为空
text: 选择一个主题
language:
label: 界面语言
msg: 不能为空
@ -1238,19 +1235,19 @@ ui:
branding:
page_title: 品牌
logo:
label: Logo (可选)
label: 网站标志(Logo)
msg: 图标不能为空。
text: 在你的网站左上方的Logo图标。使用一个高度为56长宽比大于3:1的宽长方形图像。如果留空将显示网站标题文本。
mobile_logo:
label: 移动端图标(可选)
text: The logo used on mobile version of your site. Use a wide rectangular image with a height of 56. If left blank, the image from the "logo" setting will be used.
label: 移动端 Logo
text: 在你的网站的移动版上使用的标志。使用一个高度为56的宽矩形图像。如果留空将使用 "Logo"设置中的图像。
square_icon:
label: 方形图标 (可选)
label: 方形图标
msg: 方形图标不能为空。
text: 用作元数据图标的基础的图像。最好是大于512x512。
favicon:
label: 收藏夹图标(可选)
text: A favicon for your site. To work correctly over a CDN it must be a png. Will be resized to 32x32. If left blank, "square icon" will be used.
label: 收藏夹图标
text: 网站的图标。要在 CDN 正常工作,它必须是 png。 将调整大小到32x32。如果留空将使用“方形图标”。
legal:
page_title: 法律条款
terms_of_service:
@ -1315,6 +1312,7 @@ ui:
label: 需要登录
text: 只有登录用户才能访问这个社区。
form:
optional: (选填)
empty: 不能为空
invalid: 是无效的
btn_submit: 保存
@ -1364,5 +1362,7 @@ ui:
staffs: 我们的社区工作人员
reputation: 声望值
votes: 投票
prompt:
leave_page: "确定要离开此页面?"
changes_not_save: "您的更改尚未保存"

File diff suppressed because it is too large Load Diff

View File

@ -21,6 +21,7 @@ const (
QuestionCannotDeleted = "error.question.cannot_deleted"
QuestionCannotClose = "error.question.cannot_close"
QuestionCannotUpdate = "error.question.cannot_update"
QuestionAlreadyDeleted = "error.question.already_deleted"
AnswerNotFound = "error.answer.not_found"
AnswerCannotDeleted = "error.answer.cannot_deleted"
AnswerCannotUpdate = "error.answer.cannot_update"
@ -65,4 +66,6 @@ const (
TagCannotSetSynonymAsItself = "error.tag.cannot_set_synonym_as_itself"
NotAllowedRegistration = "error.user.not_allowed_registration"
SMTPConfigFromNameCannotBeEmail = "error.smtp.config_from_name_cannot_be_email"
AdminCannotUpdateTheirPassword = "error.admin.cannot_update_their_password"
AdminCannotModifySelfStatus = "error.admin.cannot_modify_self_status"
)

View File

@ -20,6 +20,8 @@ var GlobalTrans i18n.Translator
type LangOption struct {
Label string `json:"label"`
Value string `json:"value"`
// Translation completion percentage
Progress int `json:"progress"`
}
// DefaultLangOption default language option. If user config the language is default, the language option is admin choose.
@ -47,6 +49,7 @@ func NewTranslator(c *I18n) (tr i18n.Translator, err error) {
if filepath.Ext(file.Name()) != ".yaml" && file.Name() != "i18n.yaml" {
continue
}
log.Debugf("try to read file: %s", file.Name())
buf, err := os.ReadFile(filepath.Join(c.BundleDir, file.Name()))
if err != nil {
return nil, fmt.Errorf("read file failed: %s %s", file.Name(), err)
@ -94,6 +97,11 @@ func NewTranslator(c *I18n) (tr i18n.Translator, err error) {
return nil, fmt.Errorf("i18n file parsing failed: %s", err)
}
LanguageOptions = s.LangOption
for _, option := range LanguageOptions {
if option.Progress != 100 {
option.Label = fmt.Sprintf("%s (%d%%)", option.Label, option.Progress)
}
}
return GlobalTrans, err
}

View File

@ -122,7 +122,8 @@ func Sanitizer(fl validator.FieldLevel) (res bool) {
switch field.Kind() {
case reflect.String:
filter := bluemonday.UGCPolicy()
field.SetString(filter.Sanitize(field.String()))
content := strings.Replace(filter.Sanitize(field.String()), "&amp;", "&", -1)
field.SetString(content)
return true
case reflect.Chan, reflect.Map, reflect.Slice, reflect.Array:
return field.Len() > 0

View File

@ -112,6 +112,18 @@ func (cc *CommentController) UpdateComment(ctx *gin.Context) {
req.UserID = middleware.GetLoginUserIDFromContext(ctx)
req.IsAdmin = middleware.GetIsAdminFromContext(ctx)
canList, err := cc.rankService.CheckOperationPermissions(ctx, req.UserID, []string{
permission.CommentAdd,
permission.CommentEdit,
permission.CommentDelete,
})
if err != nil {
handler.HandleResponse(ctx, err, nil)
return
}
req.CanAdd = canList[0]
req.CanEdit = canList[1]
req.CanDelete = canList[2]
can, err := cc.rankService.CheckOperationPermission(ctx, req.UserID, permission.CommentEdit, req.CommentID)
if err != nil {
handler.HandleResponse(ctx, err, nil)
@ -122,8 +134,8 @@ func (cc *CommentController) UpdateComment(ctx *gin.Context) {
return
}
err = cc.commentService.UpdateComment(ctx, req)
handler.HandleResponse(ctx, err, nil)
resp, err := cc.commentService.UpdateComment(ctx, req)
handler.HandleResponse(ctx, err, resp)
}
// GetCommentWithPage get comment page

View File

@ -41,7 +41,6 @@ func (u *LangController) GetLangMapping(ctx *gin.Context) {
// @Tags Lang
// @Produce json
// @Success 200 {object} handler.RespBody{}
// @Router /answer/api/v1/language/options [get]
// @Router /answer/admin/api/language/options [get]
func (u *LangController) GetAdminLangOptions(ctx *gin.Context) {
handler.HandleResponse(ctx, nil, translator.LanguageOptions)

View File

@ -404,22 +404,17 @@ func (tc *TemplateController) UserInfo(ctx *gin.Context) {
req := &schema.GetOtherUserInfoByUsernameReq{}
req.Username = username
userinfo, err := tc.templateRenderController.UserInfo(ctx, req)
if err != nil {
tc.Page404(ctx)
return
}
if !userinfo.Has {
tc.Page404(ctx)
return
}
siteInfo := tc.SiteInfo(ctx)
siteInfo.Canonical = fmt.Sprintf("%s/users/%s", siteInfo.General.SiteUrl, username)
siteInfo.Title = fmt.Sprintf("%s - %s", username, siteInfo.General.Name)
tc.html(ctx, http.StatusOK, "homepage.html", siteInfo, gin.H{
"userinfo": userinfo,
"bio": template.HTML(userinfo.Info.BioHTML),
"bio": template.HTML(userinfo.BioHTML),
})
}
@ -451,6 +446,7 @@ func (tc *TemplateController) html(ctx *gin.Context, code int, tpl string, siteI
if !ok {
data["path"] = ""
}
ctx.Header("X-Frame-Options", "DENY")
ctx.HTML(code, tpl, data)
}

View File

@ -5,6 +5,6 @@ import (
"golang.org/x/net/context"
)
func (q *TemplateRenderController) UserInfo(ctx context.Context, req *schema.GetOtherUserInfoByUsernameReq) (resp *schema.GetOtherUserInfoResp, err error) {
func (q *TemplateRenderController) UserInfo(ctx context.Context, req *schema.GetOtherUserInfoByUsernameReq) (resp *schema.GetOtherUserInfoByUsernameResp, err error) {
return q.userService.GetOtherUserInfoByUsername(ctx, req.Username)
}

View File

@ -157,7 +157,7 @@ func (uc *UserController) RetrievePassWord(ctx *gin.Context) {
return
}
_, _ = uc.actionService.ActionRecordAdd(ctx, schema.ActionRecordTypeFindPass, ctx.ClientIP())
_, err := uc.userService.RetrievePassWord(ctx, req)
err := uc.userService.RetrievePassWord(ctx, req)
handler.HandleResponse(ctx, err, nil)
}
@ -203,6 +203,7 @@ func (uc *UserController) UserLogout(ctx *gin.Context) {
return
}
_ = uc.authService.RemoveUserCacheInfo(ctx, accessToken)
_ = uc.authService.RemoveAdminUserCacheInfo(ctx, accessToken)
handler.HandleResponse(ctx, nil, nil)
}

View File

@ -34,6 +34,8 @@ func (uc *UserAdminController) UpdateUserStatus(ctx *gin.Context) {
return
}
req.LoginUserID = middleware.GetLoginUserIDFromContext(ctx)
err := uc.userService.UpdateUserStatus(ctx, req)
handler.HandleResponse(ctx, err, nil)
}

View File

@ -35,9 +35,11 @@ type Answer struct {
type AnswerSearch struct {
Answer
Order string `json:"order_by" ` // default or updated
Page int `json:"page" form:"page"` // Query number of pages
PageSize int `json:"page_size" form:"page_size"` // Search page size
IncludeDeleted bool `json:"include_deleted"`
LoginUserID string `json:"login_user_id"`
Order string `json:"order_by"` // default or updated
Page int `json:"page" form:"page"` // Query number of pages
PageSize int `json:"page_size" form:"page_size"` // Search page size
}
type AdminAnswerSearch struct {

View File

@ -78,7 +78,7 @@ type InitEnvironmentResp struct {
// InitBaseInfoReq init base info request
type InitBaseInfoReq struct {
Language string `validate:"required,gt=0,lte=30" json:"lang"`
SiteName string `validate:"required,gt=0,lte=30" json:"site_name"`
SiteName string `validate:"required,sanitizer,gt=0,lte=30" json:"site_name"`
SiteURL string `validate:"required,gt=0,lte=512,url" json:"site_url"`
ContactEmail string `validate:"required,email,gt=0,lte=500" json:"contact_email"`
AdminName string `validate:"required,gt=3,lte=30" json:"name"`

View File

@ -206,7 +206,9 @@ func (ar *answerRepo) SearchList(ctx context.Context, search *entity.AnswerSearc
default:
session = session.OrderBy("adopted desc,vote_count desc,created_at asc")
}
session = session.And("status = ?", entity.AnswerStatusAvailable)
if !search.IncludeDeleted {
session = session.And("status = ? OR user_id = ?", entity.AnswerStatusAvailable, search.LoginUserID)
}
session = session.Limit(search.PageSize, offset)
count, err = session.FindAndCount(&rows)

View File

@ -68,3 +68,11 @@ func (cr *captchaRepo) GetCaptcha(ctx context.Context, key string) (captcha stri
}
return captcha, nil
}
func (cr *captchaRepo) DelCaptcha(ctx context.Context, key string) (err error) {
err = cr.data.Cache.Del(ctx, key)
if err != nil {
log.Debug(err)
}
return nil
}

View File

@ -106,6 +106,7 @@ func (a *UIRouter) Register(r *gin.Engine) {
default:
filePath = UIIndexFilePath
c.Header("content-type", "text/html;charset=utf-8")
c.Header("X-Frame-Options", "DENY")
}
file, err := ui.Build.ReadFile(filePath)
if err != nil {

View File

@ -84,6 +84,7 @@ type AnswerInfo struct {
VoteStatus string `json:"vote_status"`
VoteCount int `json:"vote_count"`
QuestionInfo *QuestionInfo `json:"question_info,omitempty"`
Status int `json:"status"`
// MemberActions
MemberActions []*PermissionMemberAction `json:"member_actions"`

View File

@ -2,10 +2,9 @@ package schema
// UpdateUserStatusReq update user request
type UpdateUserStatusReq struct {
// user id
UserID string `validate:"required" json:"user_id"`
// user status
Status string `validate:"required,oneof=normal suspended deleted inactive" json:"status" enums:"normal,suspended,deleted,inactive"`
UserID string `validate:"required" json:"user_id"`
Status string `validate:"required,oneof=normal suspended deleted inactive" json:"status" enums:"normal,suspended,deleted,inactive"`
LoginUserID string `json:"-"`
}
const (

View File

@ -53,6 +53,12 @@ type UpdateCommentReq struct {
// user id
UserID string `json:"-"`
IsAdmin bool `json:"-"`
CanAdd bool `json:"-"`
// whether user can edit it
CanEdit bool `json:"-"`
// whether user can delete it
CanDelete bool `json:"-"`
}
func (req *UpdateCommentReq) Check() (errFields []*validator.FormErrorField, err error) {

View File

@ -176,11 +176,20 @@ type AdminQuestionInfo struct {
UserInfo *UserBasicInfo `json:"user_info"`
}
type OperationLevel string
const (
OperationLevelInfo OperationLevel = "info"
OperationLevelDanger OperationLevel = "danger"
OperationLevelWarning OperationLevel = "warning"
)
type Operation struct {
OperationType string `json:"operation_type"`
OperationDescription string `json:"operation_description"`
OperationMsg string `json:"operation_msg"`
OperationTime int64 `json:"operation_time"`
Type string `json:"type"`
Description string `json:"description"`
Msg string `json:"msg"`
Time int64 `json:"time"`
Level OperationLevel `json:"level"`
}
type GetCloseTypeResp struct {

View File

@ -90,7 +90,7 @@ type GetTagResp struct {
}
func (tr *GetTagResp) GetExcerpt() {
excerpt := strings.TrimSpace(tr.OriginalText)
excerpt := strings.TrimSpace(tr.ParsedText)
idx := strings.Index(excerpt, "\n")
if idx >= 0 {
excerpt = excerpt[0:idx]

View File

@ -317,7 +317,7 @@ func (req *UpdateInfoRequest) Check() (errFields []*validator.FormErrorField, er
return errFields, errors.BadRequest(reason.UsernameInvalid)
}
}
req.BioHTML = converter.Markdown2HTML(req.Bio)
req.BioHTML = converter.Markdown2BasicHTML(req.Bio)
return nil, nil
}
@ -394,7 +394,6 @@ type GetOtherUserInfoByUsernameReq struct {
type GetOtherUserInfoResp struct {
Info *GetOtherUserInfoByUsernameResp `json:"info"`
Has bool `json:"has"`
}
type UserChangeEmailSendCodeReq struct {

View File

@ -16,6 +16,7 @@ import (
type CaptchaRepo interface {
SetCaptcha(ctx context.Context, key, captcha string) (err error)
GetCaptcha(ctx context.Context, key string) (captcha string, err error)
DelCaptcha(ctx context.Context, key string) (err error)
SetActionType(ctx context.Context, ip, actionType string, amount int) (err error)
GetActionType(ctx context.Context, ip, actionType string) (amount int, err error)
DelActionType(ctx context.Context, ip, actionType string) (err error)
@ -143,6 +144,12 @@ func (cs *CaptchaService) GenerateCaptcha(ctx context.Context) (key, captchaBase
func (cs *CaptchaService) VerifyCaptcha(ctx context.Context, key, captcha string) (isCorrect bool, err error) {
realCaptcha, err := cs.captchaRepo.GetCaptcha(ctx, key)
if err != nil {
log.Error("VerifyCaptcha GetCaptcha Error", err.Error())
return false, nil
}
err = cs.captchaRepo.DelCaptcha(ctx, key)
if err != nil {
log.Error("VerifyCaptcha DelCaptcha Error", err.Error())
return false, nil
}
return strings.TrimSpace(captcha) == realCaptcha, nil

View File

@ -73,6 +73,7 @@ func (as *AnswerCommon) ShowFormat(ctx context.Context, data *entity.Answer) *sc
}
info.UserID = data.UserID
info.UpdateUserID = data.LastEditUserID
info.Status = data.Status
return &info
}

View File

@ -172,7 +172,7 @@ func (as *AnswerService) Insert(ctx context.Context, req *schema.AnswerAddReq) (
if err != nil {
log.Error("UpdateLastAnswer error", err.Error())
}
err = as.questionCommon.UpdataPostTime(ctx, req.QuestionID)
err = as.questionCommon.UpdatePostTime(ctx, req.QuestionID)
if err != nil {
return insertData.ID, err
}
@ -240,6 +240,11 @@ func (as *AnswerService) Update(ctx context.Context, req *schema.AnswerUpdateReq
return "", nil
}
if answerInfo.Status == entity.AnswerStatusDeleted {
err = errors.BadRequest(reason.AnswerCannotUpdate)
return "", err
}
//If the content is the same, ignore it
if answerInfo.OriginalText == req.Content {
return "", nil
@ -276,7 +281,7 @@ func (as *AnswerService) Update(ctx context.Context, req *schema.AnswerUpdateReq
if err = as.answerRepo.UpdateAnswer(ctx, insertData, []string{"original_text", "parsed_text", "updated_at", "last_edit_user_id"}); err != nil {
return "", err
}
err = as.questionCommon.UpdataPostTime(ctx, req.QuestionID)
err = as.questionCommon.UpdatePostTime(ctx, req.QuestionID)
if err != nil {
return insertData.ID, err
}
@ -481,6 +486,8 @@ func (as *AnswerService) SearchList(ctx context.Context, req *schema.AnswerListR
dbSearch.Page = req.Page
dbSearch.PageSize = req.PageSize
dbSearch.Order = req.Order
dbSearch.IncludeDeleted = req.CanDelete
dbSearch.LoginUserID = req.UserID
answerOriginalList, count, err := as.answerRepo.SearchList(ctx, &dbSearch)
if err != nil {
return list, count, err

View File

@ -209,24 +209,40 @@ func (cs *CommentService) RemoveComment(ctx context.Context, req *schema.RemoveC
}
// UpdateComment update comment
func (cs *CommentService) UpdateComment(ctx context.Context, req *schema.UpdateCommentReq) (err error) {
func (cs *CommentService) UpdateComment(ctx context.Context, req *schema.UpdateCommentReq) (
resp *schema.GetCommentResp, err error) {
resp = &schema.GetCommentResp{}
old, exist, err := cs.commentCommonRepo.GetComment(ctx, req.CommentID)
if err != nil {
return
}
if !exist {
return errors.BadRequest(reason.CommentNotFound)
return resp, errors.BadRequest(reason.CommentNotFound)
}
// user can edit the comment that was posted by himself before deadline.
if !req.IsAdmin && (time.Now().After(old.CreatedAt.Add(constant.CommentEditDeadline))) {
return errors.BadRequest(reason.CommentCannotEditAfterDeadline)
return resp, errors.BadRequest(reason.CommentCannotEditAfterDeadline)
}
comment := &entity.Comment{}
_ = copier.Copy(comment, req)
comment.ID = req.CommentID
return cs.commentRepo.UpdateComment(ctx, comment)
resp.SetFromComment(comment)
resp.MemberActions = permission.GetCommentPermission(ctx, req.UserID, resp.UserID,
time.Now(), req.CanEdit, req.CanDelete)
userInfo, exist, err := cs.userCommon.GetUserBasicInfoByID(ctx, resp.UserID)
if err != nil {
return nil, err
}
if exist {
resp.Username = userInfo.Username
resp.UserDisplayName = userInfo.DisplayName
resp.UserAvatar = userInfo.Avatar
resp.UserStatus = userInfo.Status
}
return resp, cs.commentRepo.UpdateComment(ctx, comment)
}
// GetComment get comment one

View File

@ -89,6 +89,7 @@ func (ds *DashboardService) StatisticalByCache(ctx context.Context) (*schema.Das
}
startTime := time.Now().Unix() - schema.AppStartTime.Unix()
dashboardInfo.AppStartTime = fmt.Sprintf("%d", startTime)
dashboardInfo.VersionInfo.Version = constant.Version
return dashboardInfo, nil
}

View File

@ -112,14 +112,14 @@ func (qs *QuestionCommon) UpdateLastAnswer(ctx context.Context, questionID, Answ
return qs.questionRepo.UpdateLastAnswer(ctx, question)
}
func (qs *QuestionCommon) UpdataPostTime(ctx context.Context, questionID string) error {
func (qs *QuestionCommon) UpdatePostTime(ctx context.Context, questionID string) error {
questioninfo := &entity.Question{}
now := time.Now()
questioninfo.ID = questionID
questioninfo.PostUpdateTime = now
return qs.questionRepo.UpdateQuestion(ctx, questioninfo, []string{"post_update_time"})
}
func (qs *QuestionCommon) UpdataPostSetTime(ctx context.Context, questionID string, setTime time.Time) error {
func (qs *QuestionCommon) UpdatePostSetTime(ctx context.Context, questionID string, setTime time.Time) error {
questioninfo := &entity.Question{}
questioninfo.ID = questionID
questioninfo.PostUpdateTime = setTime
@ -148,7 +148,7 @@ func (qs *QuestionCommon) Info(ctx context.Context, questionID string, loginUser
return showinfo, err
}
if !has {
return showinfo, errors.BadRequest(reason.QuestionNotFound)
return showinfo, errors.NotFound(reason.QuestionNotFound)
}
showinfo = qs.ShowFormat(ctx, dbinfo)
@ -170,10 +170,11 @@ func (qs *QuestionCommon) Info(ctx context.Context, questionID string, loginUser
log.Error("json.Unmarshal QuestionCloseJson error", err.Error())
} else {
operation := &schema.Operation{}
operation.OperationType = closeinfo.Name
operation.OperationDescription = closeinfo.Description
operation.OperationMsg = closemsg.CloseMsg
operation.OperationTime = metainfo.CreatedAt.Unix()
operation.Type = closeinfo.Name
operation.Description = closeinfo.Description
operation.Msg = closemsg.CloseMsg
operation.Time = metainfo.CreatedAt.Unix()
operation.Level = schema.OperationLevelInfo
showinfo.Operation = operation
}

View File

@ -470,6 +470,10 @@ func (qs *QuestionService) UpdateQuestion(ctx context.Context, req *schema.Quest
if !has {
return
}
if dbinfo.Status == entity.QuestionStatusDeleted {
err = errors.BadRequest(reason.QuestionCannotUpdate)
return nil, err
}
now := time.Now()
question := &entity.Question{}
@ -614,12 +618,23 @@ func (qs *QuestionService) GetQuestion(ctx context.Context, questionID, userID s
if err != nil {
return
}
// If the question is deleted, only the administrator and the author can view it
if question.Status == entity.QuestionStatusDeleted && !per.CanReopen && question.UserID != userID {
return nil, errors.NotFound(reason.QuestionNotFound)
}
if question.Status != entity.QuestionStatusClosed {
per.CanReopen = false
}
if question.Status == entity.QuestionStatusClosed {
per.CanClose = false
}
if question.Status == entity.QuestionStatusDeleted {
operation := &schema.Operation{}
operation.Msg = translator.Tr(handler.GetLangByCtx(ctx), reason.QuestionAlreadyDeleted)
operation.Level = schema.OperationLevelDanger
question.Operation = operation
}
question.Description = htmltext.FetchExcerpt(question.HTML, "...", 240)
question.MemberActions = permission.GetQuestionPermission(ctx, userID, question.UserID,
per.CanEdit, per.CanDelete, per.CanClose, per.CanReopen)

View File

@ -191,7 +191,7 @@ func (rs *RevisionService) revisionAuditAnswer(ctx context.Context, revisionitem
if saveerr != nil {
return saveerr
}
saveerr = rs.questionCommon.UpdataPostSetTime(ctx, answerinfo.QuestionID, PostUpdateTime)
saveerr = rs.questionCommon.UpdatePostSetTime(ctx, answerinfo.QuestionID, PostUpdateTime)
if saveerr != nil {
return saveerr
}

View File

@ -102,7 +102,7 @@ func (ts *TagService) GetTagInfo(ctx context.Context, req *schema.GetTagInfoReq)
return nil, err
}
if !exist {
return nil, errors.BadRequest(reason.TagNotFound)
return nil, errors.NotFound(reason.TagNotFound)
}
resp = &schema.GetTagResp{}
@ -113,7 +113,7 @@ func (ts *TagService) GetTagInfo(ctx context.Context, req *schema.GetTagInfoReq)
return nil, err
}
if !exist {
return nil, errors.BadRequest(reason.TagNotFound)
return nil, errors.NotFound(reason.TagNotFound)
}
resp.MainTagSlugName = tagInfo.SlugName
}

View File

@ -61,6 +61,10 @@ func NewUserAdminService(
// UpdateUserStatus update user
func (us *UserAdminService) UpdateUserStatus(ctx context.Context, req *schema.UpdateUserStatusReq) (err error) {
// Admin cannot modify their status
if req.UserID == req.LoginUserID {
return errors.BadRequest(reason.AdminCannotModifySelfStatus)
}
userInfo, exist, err := us.userRepo.GetUserInfo(ctx, req.UserID)
if err != nil {
return
@ -153,6 +157,10 @@ func (us *UserAdminService) AddUser(ctx context.Context, req *schema.AddUserReq)
// UpdateUserPassword update user password
func (us *UserAdminService) UpdateUserPassword(ctx context.Context, req *schema.UpdateUserPasswordReq) (err error) {
// Users cannot modify their password
if req.UserID == req.LoginUserID {
return errors.BadRequest(reason.AdminCannotUpdateTheirPassword)
}
userInfo, exist, err := us.userRepo.GetUserInfo(ctx, req.UserID)
if err != nil {
return err

View File

@ -86,19 +86,17 @@ func (us *UserService) GetUserInfoByUserID(ctx context.Context, token, userID st
}
func (us *UserService) GetOtherUserInfoByUsername(ctx context.Context, username string) (
resp *schema.GetOtherUserInfoResp, err error,
resp *schema.GetOtherUserInfoByUsernameResp, err error,
) {
userInfo, exist, err := us.userRepo.GetByUsername(ctx, username)
if err != nil {
return nil, err
}
resp = &schema.GetOtherUserInfoResp{}
if !exist {
return resp, nil
return nil, errors.NotFound(reason.UserNotFound)
}
resp.Has = true
resp.Info = &schema.GetOtherUserInfoByUsernameResp{}
resp.Info.GetFromUserEntity(userInfo)
resp = &schema.GetOtherUserInfoByUsernameResp{}
resp.GetFromUserEntity(userInfo)
return resp, nil
}
@ -149,13 +147,13 @@ func (us *UserService) EmailLogin(ctx context.Context, req *schema.UserEmailLogi
}
// RetrievePassWord .
func (us *UserService) RetrievePassWord(ctx context.Context, req *schema.UserRetrievePassWordRequest) (string, error) {
func (us *UserService) RetrievePassWord(ctx context.Context, req *schema.UserRetrievePassWordRequest) error {
userInfo, has, err := us.userRepo.GetByEmail(ctx, req.Email)
if err != nil {
return "", err
return err
}
if !has {
return "", errors.BadRequest(reason.UserNotFound)
return nil
}
// send email
@ -167,10 +165,10 @@ func (us *UserService) RetrievePassWord(ctx context.Context, req *schema.UserRet
verifyEmailURL := fmt.Sprintf("%s/users/password-reset?code=%s", us.getSiteUrl(ctx), code)
title, body, err := us.emailService.PassResetTemplate(ctx, verifyEmailURL)
if err != nil {
return "", err
return err
}
go us.emailService.SendAndSaveCode(ctx, req.Email, title, body, code, data.ToJSONString())
return code, nil
return nil
}
// UseRePassword

View File

@ -35,6 +35,17 @@ func Markdown2HTML(source string) string {
return buf.String()
}
// Markdown2BasicHTML convert markdown to html ,Only basic syntax can be used
func Markdown2BasicHTML(source string) string {
content := Markdown2HTML(source)
filter := bluemonday.NewPolicy()
filter.AllowElements("p", "b", "br")
filter.AllowAttrs("src").OnElements("img")
filter.AddSpaceWhenStrippingTag(true)
content = filter.Sanitize(content)
return content
}
type DangerousHTMLFilterExtension struct {
}

View File

@ -6,6 +6,21 @@ export const LOGGED_USER_STORAGE_KEY = '_a_lui_';
export const LOGGED_TOKEN_STORAGE_KEY = '_a_ltk_';
export const REDIRECT_PATH_STORAGE_KEY = '_a_rp_';
export const CAPTCHA_CODE_STORAGE_KEY = '_a_captcha_';
export const DRAFT_QUESTION_STORAGE_KEY = '_a_dq_';
export const DRAFT_ANSWER_STORAGE_KEY = '_a_da_';
export const DRAFT_TIMESIGH_STORAGE_KEY = '|_a_t_s_|';
export const IGNORE_PATH_LIST = [
'/users/login',
'/users/register',
'/users/account-recovery',
'/users/change-email',
'/users/password-reset',
'/users/account-activation',
'/users/account-activation/success',
'/users/account-activation/failed',
'/users/confirm-new-email',
];
export const ADMIN_LIST_STATUS = {
// normal;

View File

@ -5,7 +5,6 @@ import { Link } from 'react-router-dom';
import classNames from 'classnames';
import { unionBy } from 'lodash';
import { marked } from 'marked';
import * as Types from '@/common/interface';
import { Modal } from '@/components';
@ -108,15 +107,11 @@ const Comment = ({ objectId, mode, commentId }) => {
const users = matchedUsers(item.value);
const userNames = unionBy(users.map((user) => user.userName));
const commentMarkDown = parseUserInfo(item.value);
const html = marked.parse(commentMarkDown);
// if (!commentMarkDown || !html) {
// return;
// }
const params = {
object_id: objectId,
original_text: commentMarkDown,
mention_username_list: userNames,
parsed_text: html,
...(item.type === 'reply'
? {
reply_comment_id: item.comment_id,
@ -128,13 +123,13 @@ const Comment = ({ objectId, mode, commentId }) => {
return updateComment({
...params,
comment_id: item.comment_id,
}).then(() => {
}).then((res) => {
setComments(
comments.map((comment) => {
if (comment.comment_id === item.comment_id) {
comment.showEdit = false;
comment.parsed_text = html;
comment.original_text = item.value;
comment.parsed_text = res.parsed_text;
comment.original_text = res.original_text;
}
return comment;
}),

View File

@ -69,7 +69,10 @@ const Index: FC<Props> = ({
{objectType !== 'answer' && opts?.showTitle && (
<h5
dangerouslySetInnerHTML={{
__html: diffText(newData.title, oldData?.title),
__html: diffText(
newData.title?.replace(/</gi, '&lt;'),
oldData?.title?.replace(/</gi, '&lt;'),
),
}}
className="mb-3"
/>

View File

@ -114,19 +114,8 @@ export function htmlRender(el: HTMLElement | null) {
},
);
el.querySelectorAll('table').forEach((table) => {
if (
(table.parentNode as HTMLDivElement)?.classList.contains(
'table-responsive',
)
) {
return;
}
table.classList.add('table', 'table-bordered');
const div = document.createElement('div');
div.className = 'table-responsive';
table.parentNode?.replaceChild(div, table);
div.appendChild(table);
});
// remove change table style to htmlToReact function
/**
* @description: You modify the DOM with other scripts after React has rendered the DOM. This way, on the next render cycle (re-render), React cannot find the DOM node it rendered before, because it has been modified or removed by other scripts.
*/
}

View File

@ -71,6 +71,10 @@ const Header: FC = () => {
};
const onLoginClick = (evt) => {
evt.preventDefault();
if (location.pathname === '/users/login') {
window.location.reload();
return;
}
floppyNavigation.navigateToLogin((loginPath) => {
navigate(loginPath, { replace: true });
});

View File

@ -206,7 +206,6 @@ const SchemaForm: ForwardRefRenderFunction<IRef, IProps> = (
const errors = requiredValidator();
if (errors.length > 0) {
formData = errors.reduce((acc, cur) => {
console.log('schema.properties[cur]', cur);
acc[cur] = {
...formData[cur],
isInvalid: true,

View File

@ -9,6 +9,7 @@ import useUserModal from './useUserModal';
import useChangePasswordModal from './useChangePasswordModal';
import usePageTags from './usePageTags';
import usePromptWithUnload from './usePrompt';
import useImgViewer from './useImgViewer';
export {
useTagModal,
@ -22,4 +23,5 @@ export {
useChangePasswordModal,
usePageTags,
usePromptWithUnload,
useImgViewer,
};

View File

@ -0,0 +1,78 @@
import { useLayoutEffect, useState, MouseEvent, useEffect } from 'react';
import { Modal } from 'react-bootstrap';
import { useLocation } from 'react-router-dom';
import ReactDOM from 'react-dom/client';
const div = document.createElement('div');
const root = ReactDOM.createRoot(div);
const useImgViewer = () => {
const location = useLocation();
const [visible, setVisible] = useState(false);
const [imgSrc, setImgSrc] = useState('');
const onClose = () => {
setVisible(false);
setImgSrc('');
};
const checkIfInLink = (target) => {
let ret = false;
let el = target.parentElement;
while (el) {
if (el.nodeName.toLowerCase() === 'a') {
ret = true;
break;
}
el = el.parentElement;
}
return ret;
};
const checkClickForImgView = (evt: MouseEvent<HTMLElement>) => {
const { target } = evt;
// @ts-ignore
if (target.nodeName.toLowerCase() !== 'img') {
return;
}
const img = target as HTMLImageElement;
if (!img.naturalWidth || !img.naturalHeight) {
img.classList.add('broken');
return;
}
const src = img.currentSrc || img.src;
if (src && checkIfInLink(img) === false) {
setImgSrc(src);
setVisible(true);
}
};
useLayoutEffect(() => {
root.render(
<Modal
show={visible}
fullscreen
centered
scrollable
contentClassName="bg-transparent"
onHide={onClose}>
<Modal.Body onClick={onClose} className="p-0 d-flex">
<img
className="cursor-zoom-out img-fluid m-auto"
src={imgSrc}
alt={imgSrc}
/>
</Modal.Body>
</Modal>,
);
});
useEffect(() => {
onClose();
}, [location]);
return {
onClose,
checkClickForImgView,
};
};
export default useImgViewer;

View File

@ -6,7 +6,7 @@ import {
import { useTranslation } from 'react-i18next';
// https://gist.github.com/chaance/2f3c14ec2351a175024f62fd6ba64aa6
// The link above is an example of implementing usePromt with useBlocer.
// The link above is an example of implementing usePrompt with useBlocker.
interface PromptProps {
when: boolean;
beforeUnload?: boolean;

View File

@ -47,7 +47,11 @@ const useReportModal = (callback?: () => void) => {
setShow(true);
});
};
const asyncCallback = () => {
setTimeout(() => {
callback?.();
});
};
const handleRadio = (val) => {
setInvalidState(false);
setContent({
@ -93,8 +97,8 @@ const useReportModal = (callback?: () => void) => {
close_type: reportType.type,
close_msg: content.value,
}).then(() => {
callback?.();
onClose();
asyncCallback();
});
return;
}
@ -109,8 +113,8 @@ const useReportModal = (callback?: () => void) => {
msg: t('flag_success', { keyPrefix: 'toast' }),
variant: 'warning',
});
callback?.();
onClose();
asyncCallback();
});
}
@ -121,8 +125,8 @@ const useReportModal = (callback?: () => void) => {
flagged_type: reportType.type,
id: params.id,
}).then(() => {
callback?.();
onClose();
asyncCallback();
});
}
};

View File

@ -120,6 +120,14 @@ a {
cursor: pointer;
}
.cursor-zoom-out {
cursor: zoom-out !important;
}
img:not(a img, img.broken) {
cursor: zoom-in;
}
.resize-none {
resize: none;
}

View File

@ -1,17 +1,30 @@
import { useEffect } from 'react';
import { Container, Button } from 'react-bootstrap';
import { Link } from 'react-router-dom';
import { useTranslation } from 'react-i18next';
const Index = () => {
const { t } = useTranslation('translation', { keyPrefix: 'page_404' });
useEffect(() => {
// auto height of container
const pageWrap = document.querySelector('.page-wrap');
pageWrap.style.display = 'contents';
return () => {
pageWrap.style.display = 'block';
};
}, []);
return (
<Container className="d-flex flex-column justify-content-center align-items-center page-wrap">
<Container
className="d-flex flex-column justify-content-center align-items-center"
style={{ flex: 1 }}>
<div
className="mb-4 text-secondary"
style={{ fontSize: '120px', lineHeight: 1.2 }}>
(=x=)
</div>
<div className="text-center mb-4">{t('desc')}</div>
<h4 className="text-center">{t('http_error')}</h4>
<div className="text-center mb-3 fs-5">{t('desc')}</div>
<div className="text-center">
<Button as={Link} to="/" variant="link">
{t('back_home')}

View File

@ -1,9 +1,19 @@
import { useEffect } from 'react';
import { Container, Button } from 'react-bootstrap';
import { Link } from 'react-router-dom';
import { useTranslation } from 'react-i18next';
const Index = () => {
const { t } = useTranslation('translation', { keyPrefix: 'page_50X' });
useEffect(() => {
// auto height of container
const pageWrap = document.querySelector('.page-wrap');
pageWrap.style.display = 'contents';
return () => {
pageWrap.style.display = 'block';
};
}, []);
return (
<Container className="d-flex flex-column justify-content-center align-items-center page-wrap">
<div
@ -11,7 +21,9 @@ const Index = () => {
style={{ fontSize: '120px', lineHeight: 1.2 }}>
(=T^T=)
</div>
<div className="text-center mb-3">{t('desc')}</div>
<h4 className="text-center">{t('http_error')}</h4>
<div className="text-center mb-3 fs-5">{t('desc')}</div>
<div className="text-center">
<Button as={Link} to="/" variant="link">
{t('back_home')}

View File

@ -3,6 +3,7 @@ import { Form, Button } from 'react-bootstrap';
import { useTranslation } from 'react-i18next';
import type { FormDataType } from '@/common/interface';
import Pattern from '@/common/pattern';
import Progress from '../Progress';
interface Props {
@ -54,8 +55,7 @@ const Index: FC<Props> = ({ visible, data, changeCallback, nextCallback }) => {
};
}
const mailReg = /^[a-zA-Z0-9_-]+@[a-zA-Z0-9_-]+(\.[a-zA-Z0-9_-]+)+$/;
if (contact_email.value && !contact_email.value.match(mailReg)) {
if (contact_email.value && !Pattern.email.test(contact_email.value)) {
bol = false;
data.contact_email = {
value: contact_email.value,
@ -91,7 +91,7 @@ const Index: FC<Props> = ({ visible, data, changeCallback, nextCallback }) => {
};
}
if (email.value && !email.value.match(mailReg)) {
if (email.value && !Pattern.email.test(email.value)) {
bol = false;
data.email = {
value: email.value,

View File

@ -1,10 +1,10 @@
import { FC, memo } from 'react';
import { Outlet } from 'react-router-dom';
import { FC, memo, useEffect } from 'react';
import { Outlet, useLocation } from 'react-router-dom';
import { HelmetProvider } from 'react-helmet-async';
import { SWRConfig } from 'swr';
import { toastStore, loginToContinueStore } from '@/stores';
import { toastStore, loginToContinueStore, notFoundStore } from '@/stores';
import {
Header,
Footer,
@ -14,13 +14,23 @@ import {
PageTags,
} from '@/components';
import { LoginToContinueModal } from '@/components/Modal';
import { useImgViewer } from '@/hooks';
import Component404 from '@/pages/404';
const Layout: FC = () => {
const location = useLocation();
const { msg: toastMsg, variant, clear: toastClear } = toastStore();
const closeToast = () => {
toastClear();
};
const { visible: show404, hide: notFoundHide } = notFoundStore();
const imgViewer = useImgViewer();
const { show: showLoginToContinueModal } = loginToContinueStore();
useEffect(() => {
notFoundHide();
}, [location]);
return (
<HelmetProvider>
<PageTags />
@ -30,8 +40,11 @@ const Layout: FC = () => {
revalidateOnFocus: false,
}}>
<Header />
<div className="position-relative page-wrap">
<Outlet />
{/* eslint-disable-next-line jsx-a11y/click-events-have-key-events */}
<div
className="position-relative page-wrap"
onClick={imgViewer.checkClickForImgView}>
{show404 ? <Component404 /> : <Outlet />}
</div>
<Toast msg={toastMsg} variant={variant} onClose={closeToast} />
<Footer />

View File

@ -10,6 +10,7 @@ import { isEqual } from 'lodash';
import { usePageTags, usePromptWithUnload } from '@/hooks';
import { Editor, EditorRef, TagSelector } from '@/components';
import type * as Type from '@/common/interface';
import { DRAFT_QUESTION_STORAGE_KEY } from '@/common/constants';
import {
saveQuestion,
questionDetail,
@ -19,7 +20,7 @@ import {
useQueryQuestionByTitle,
getTagsBySlugName,
} from '@/services';
import { handleFormError } from '@/utils';
import { handleFormError, SaveDraft, storageExpires } from '@/utils';
import { pathFactory } from '@/router/pathFactory';
import SearchQuestion from './components/SearchQuestion';
@ -32,6 +33,8 @@ interface FormDataItem {
edit_summary: Type.FormValue<string>;
}
const saveDraft = new SaveDraft({ type: 'question' });
const Ask = () => {
const initFormData = {
title: {
@ -66,6 +69,7 @@ const Ask = () => {
const [checked, setCheckState] = useState(false);
const [contentChanged, setContentChanged] = useState(false);
const [focusType, setForceType] = useState('');
const [hasDraft, setHasDraft] = useState(false);
const resetForm = () => {
setFormData(initFormData);
setCheckState(false);
@ -98,6 +102,34 @@ const Ask = () => {
isEdit ? '' : formData.title.value,
);
const removeDraft = () => {
saveDraft.save.cancel();
saveDraft.remove();
setHasDraft(false);
};
useEffect(() => {
if (!qid) {
initQueryTags();
const draft = storageExpires.get(DRAFT_QUESTION_STORAGE_KEY);
if (draft) {
formData.title.value = draft.title;
formData.content.value = draft.content;
formData.tags.value = draft.tags;
formData.answer.value = draft.answer;
setCheckState(Boolean(draft.answer));
setHasDraft(true);
setFormData({ ...formData });
} else {
resetForm();
}
}
return () => {
resetForm();
};
}, [qid]);
useEffect(() => {
const { title, tags, content, answer } = formData;
const { title: editTitle, tags: editTags, content: editContent } = immData;
@ -118,11 +150,21 @@ const Ask = () => {
}
return;
}
// write
if (title.value || tags.value.length > 0 || content.value || answer.value) {
// save draft
saveDraft.save({
params: {
title: title.value,
tags: tags.value,
content: content.value,
answer: answer.value,
},
callback: () => setHasDraft(true),
});
setContentChanged(true);
} else {
removeDraft();
setContentChanged(false);
}
}, [formData]);
@ -131,12 +173,6 @@ const Ask = () => {
when: contentChanged,
});
useEffect(() => {
if (!isEdit) {
resetForm();
initQueryTags();
}
}, [isEdit]);
const { data: revisions = [] } = useQueryRevisions(qid);
useEffect(() => {
@ -191,6 +227,14 @@ const Ask = () => {
},
});
const deleteDraft = () => {
const res = window.confirm(t('discard_confirm', { keyPrefix: 'draft' }));
if (res) {
removeDraft();
resetForm();
}
};
const handleSubmit = async (event: React.FormEvent<HTMLFormElement>) => {
setContentChanged(false);
event.preventDefault();
@ -248,6 +292,7 @@ const Ask = () => {
navigate(pathFactory.questionLanding(id));
}
}
removeDraft();
}
};
const backPage = () => {
@ -376,10 +421,17 @@ const Ask = () => {
<Button type="submit" className="me-2">
{isEdit ? t('btn_save_edits') : t('btn_post_question')}
</Button>
{isEdit && (
<Button variant="link" onClick={backPage}>
{t('cancel', { keyPrefix: 'btns' })}
</Button>
)}
<Button variant="link" onClick={backPage}>
{t('cancel', { keyPrefix: 'btns' })}
</Button>
{hasDraft && (
<Button variant="link" onClick={deleteDraft}>
{t('discard_draft', { keyPrefix: 'btns' })}
</Button>
)}
</div>
)}
{!isEdit && (
@ -411,7 +463,6 @@ const Ask = () => {
}}
/>
<Form.Control
value={formData.answer.value}
type="text"
isInvalid={formData.answer.isInvalid}
hidden
@ -424,9 +475,14 @@ const Ask = () => {
</>
)}
{checked && (
<Button type="submit" className="mt-3">
{t('post_question&answer')}
</Button>
<div className="mt-3">
<Button type="submit">{t('post_question&answer')}</Button>
{hasDraft && (
<Button variant="link" className="ms-2" onClick={deleteDraft}>
{t('discard_draft', { keyPrefix: 'btns' })}
</Button>
)}
</div>
)}
</Form>
</Col>

View File

@ -10,38 +10,38 @@ interface Props {
const Index: FC<Props> = ({ data }) => {
const { t } = useTranslation();
return (
<Alert className="mb-4" variant="info">
<div>
{data.operation_msg.indexOf('http') > -1 ? (
<p>
{data.operation_description}{' '}
<a href={data.operation_msg} style={{ color: '#055160' }}>
<strong>{t('question_detail.show_exist')}</strong>
</a>
</p>
) : (
<p>
{data.operation_msg
? data.operation_msg
: data.operation_description}
</p>
)}
<div className="fs-14">
{t('question_detail.closed_in')}{' '}
<time
dateTime={dayjs.unix(data.operation_time).tz().toISOString()}
title={dayjs
.unix(data.operation_time)
.tz()
.format(t('dates.long_date_with_time'))}>
{dayjs
.unix(data.operation_time)
.tz()
.format(t('dates.long_date_with_year'))}
</time>
.
<Alert className="mb-4" variant={data.level}>
{data.level === 'info' ? (
<div>
{data.msg.indexOf('http') > -1 ? (
<p>
{data.description}{' '}
<a href={data.msg} style={{ color: '#055160' }}>
<strong>{t('question_detail.show_exist')}</strong>
</a>
</p>
) : (
<p>{data.msg ? data.msg : data.description}</p>
)}
<div className="fs-14">
{t('question_detail.closed_in')}{' '}
<time
dateTime={dayjs.unix(data.time).tz().toISOString()}
title={dayjs
.unix(data.time)
.tz()
.format(t('dates.long_date_with_time'))}>
{dayjs
.unix(data.time)
.tz()
.format(t('dates.long_date_with_year'))}
</time>
.
</div>
</div>
</div>
) : (
data.msg
)}
</Alert>
);
};

View File

@ -1,5 +1,5 @@
import { memo, FC, useEffect, useRef } from 'react';
import { Button } from 'react-bootstrap';
import { Button, Alert } from 'react-bootstrap';
import { useTranslation } from 'react-i18next';
import { Link, useSearchParams } from 'react-router-dom';
@ -72,6 +72,11 @@ const Index: FC<Props> = ({
}
return (
<div id={data.id} ref={answerRef} className="answer-item py-4">
{data.status === 10 && (
<Alert variant="danger" className="mb-4">
{t('post_deleted', { keyPrefix: 'messages' })}
</Alert>
)}
<article
dangerouslySetInnerHTML={{ __html: data?.html }}
className="fmt text-break text-wrap"

View File

@ -1,4 +1,4 @@
import { memo, useState, FC } from 'react';
import { memo, useState, FC, useEffect } from 'react';
import { Form, Button } from 'react-bootstrap';
import { useTranslation } from 'react-i18next';
@ -9,7 +9,8 @@ import { usePromptWithUnload } from '@/hooks';
import { Editor, Modal, TextArea } from '@/components';
import { FormDataType } from '@/common/interface';
import { postAnswer } from '@/services';
import { guard, handleFormError } from '@/utils';
import { guard, handleFormError, SaveDraft, storageExpires } from '@/utils';
import { DRAFT_ANSWER_STORAGE_KEY } from '@/common/constants';
interface Props {
visible?: boolean;
@ -21,6 +22,8 @@ interface Props {
callback?: (obj) => void;
}
const saveDraft = new SaveDraft({ type: 'answer' });
const Index: FC<Props> = ({ visible = false, data, callback }) => {
const { t } = useTranslation('translation', {
keyPrefix: 'question_detail.write_answer',
@ -35,11 +38,51 @@ const Index: FC<Props> = ({ visible = false, data, callback }) => {
const [showEditor, setShowEditor] = useState<boolean>(visible);
const [focusType, setFocusType] = useState('');
const [editorFocusState, setEditorFocusState] = useState(false);
const [hasDraft, setHasDraft] = useState(false);
usePromptWithUnload({
when: Boolean(formData.content.value),
});
const removeDraft = () => {
// immediately remove debounced save
saveDraft.save.cancel();
saveDraft.remove();
setHasDraft(false);
};
useEffect(() => {
const draft = storageExpires.get(DRAFT_ANSWER_STORAGE_KEY);
if (draft?.questionId === data.qid && draft?.content) {
setFormData({
content: {
value: draft.content,
isInvalid: false,
errorMsg: '',
},
});
setShowEditor(true);
setHasDraft(true);
}
}, []);
useEffect(() => {
const draft = storageExpires.get(DRAFT_ANSWER_STORAGE_KEY);
const { content } = formData;
if (content.value) {
// save Draft
saveDraft.save({
questionId: data?.qid,
content: content.value,
});
setHasDraft(true);
} else if (draft?.questionId === data.qid && !content.value) {
removeDraft();
}
}, [formData.content.value]);
const checkValidated = (): boolean => {
let bol = true;
const { content } = formData;
@ -65,6 +108,24 @@ const Index: FC<Props> = ({ visible = false, data, callback }) => {
return bol;
};
const resetForm = () => {
setFormData({
content: {
value: '',
isInvalid: false,
errorMsg: '',
},
});
};
const deleteDraft = () => {
const res = window.confirm(t('discard_confirm', { keyPrefix: 'draft' }));
if (res) {
removeDraft();
resetForm();
}
};
const handleSubmit = () => {
if (!guard.tryNormalLogged(true)) {
return;
@ -86,6 +147,7 @@ const Index: FC<Props> = ({ visible = false, data, callback }) => {
errorMsg: '',
},
});
removeDraft();
callback?.(res.info);
})
.catch((ex) => {
@ -128,7 +190,6 @@ const Index: FC<Props> = ({ visible = false, data, callback }) => {
setShowEditor(true);
setEditorFocusState(true);
};
return (
<Form noValidate className="mt-4">
{(!data.answered || showEditor) && (
@ -187,6 +248,11 @@ const Index: FC<Props> = ({ visible = false, data, callback }) => {
) : (
<Button onClick={clickBtn}>{t('btn_name')}</Button>
)}
{hasDraft && (
<Button variant="link" className="ms-2" onClick={deleteDraft}>
{t('discard_draft', { keyPrefix: 'btns' })}
</Button>
)}
</Form>
);
};

View File

@ -56,6 +56,7 @@ const Index = () => {
const { setUsers } = usePageUsers();
const userInfo = loggedUserInfoStore((state) => state.user);
const isAuthor = userInfo?.username === question?.user_info?.username;
const isAdmin = userInfo?.is_admin;
const isLogged = Boolean(userInfo?.access_token);
const { state: locationState } = useLocation();
@ -76,7 +77,22 @@ const Index = () => {
page_size: 999,
});
if (res) {
setAnswers(res);
res.list = res.list?.filter((v) => {
// delete answers pnly show to author and admin and has searchparams aid
if (v.status === 10) {
if (
(v?.user_info.username === userInfo?.username || isAdmin) &&
aid === v.id
) {
return v;
}
return null;
}
return v;
});
setAnswers({ ...res, count: res.list.length });
if (page > 0 || order) {
// scroll into view;
const element = document.getElementById('answerHeader');
@ -183,9 +199,7 @@ const Index = () => {
<Container className="pt-4 mt-2 mb-5 questionDetailPage">
<Row className="justify-content-center">
<Col xxl={7} lg={8} sm={12} className="mb-5 mb-md-0">
{question?.operation?.operation_type && (
<Alert data={question.operation} />
)}
{question?.operation?.level && <Alert data={question.operation} />}
{isLoading ? (
<ContentLoader />
) : (

View File

@ -18,7 +18,7 @@ const Index: FC<Props> = ({ visible, introduction, data }) => {
<h5 className="mb-3">{t('about_me')}</h5>
{introduction ? (
<div
className="mb-4 text-break"
className="mb-4 text-break fmt"
dangerouslySetInnerHTML={{ __html: introduction }}
/>
) : (

View File

@ -11,6 +11,7 @@ import {
usePersonalTop,
usePersonalListByTabName,
} from '@/services';
import type { UserInfoRes } from '@/common/interface';
import {
UserInfo,
@ -47,8 +48,8 @@ const Personal: FC = () => {
tabName,
);
let pageTitle = '';
if (userInfo) {
pageTitle = `${userInfo.info.display_name} (${userInfo.info.username})`;
if (userInfo?.username) {
pageTitle = `${userInfo?.display_name} (${userInfo?.username})`;
}
const { count = 0, list = [] } = listData?.[tabName] || {};
usePageTags({
@ -57,11 +58,11 @@ const Personal: FC = () => {
return (
<Container className="pt-4 mt-2 mb-5">
<Row className="justify-content-center">
{userInfo?.info?.status !== 'normal' && userInfo?.info?.status_msg && (
<Alert data={userInfo?.info.status_msg} />
{userInfo?.status !== 'normal' && userInfo?.status_msg && (
<Alert data={userInfo?.status_msg} />
)}
<Col xxl={7} lg={8} sm={12}>
<UserInfo data={userInfo?.info} />
<UserInfo data={userInfo as UserInfoRes} />
</Col>
<Col
xxl={3}
@ -88,11 +89,11 @@ const Personal: FC = () => {
<Col xxl={7} lg={8} sm={12}>
<Overview
visible={tabName === 'overview'}
introduction={userInfo?.info?.bio_html}
introduction={userInfo?.bio_html || ''}
data={topData}
/>
<ListHead
count={tabName === 'reputation' ? userInfo?.info?.rank : count}
count={tabName === 'reputation' ? Number(userInfo?.rank) : count}
sort={order}
visible={tabName !== 'overview'}
tabName={tabName}
@ -120,17 +121,14 @@ const Personal: FC = () => {
</Col>
<Col xxl={3} lg={4} sm={12} className="mt-5 mt-lg-0">
<h5 className="mb-3">{t('stats')}</h5>
{userInfo?.info && (
{userInfo?.created_at && (
<>
<div className="text-secondary">
<FormatTime
time={userInfo.info.created_at}
preFix={t('joined')}
/>
<FormatTime time={userInfo.created_at} preFix={t('joined')} />
</div>
<div className="text-secondary">
<FormatTime
time={userInfo.info.last_login_date}
time={userInfo.last_login_date}
preFix={t('last_login')}
/>
</div>

View File

@ -0,0 +1,8 @@
import Error50X from '@/pages/50X';
// import Page404 from '@/pages/404';
const Index = () => {
return <Error50X />;
};
export default Index;

View File

@ -2,9 +2,10 @@ import { Suspense, lazy } from 'react';
import { RouteObject } from 'react-router-dom';
import Layout from '@/pages/Layout';
import ErrorBoundary from '@/pages/50X';
import baseRoutes, { RouteNode } from '@/router/routes';
import RouteGuard from '@/router/RouteGuard';
import baseRoutes, { RouteNode } from './routes';
import RouteGuard from './RouteGuard';
import RouteErrorBoundary from './RouteErrorBoundary';
const routes: RouteNode[] = [];
@ -18,7 +19,7 @@ const routeWrapper = (routeNodes: RouteNode[], root: RouteNode[]) => {
) : (
<Layout />
);
rn.errorElement = <ErrorBoundary />;
rn.errorElement = <RouteErrorBoundary />;
} else {
/**
* cannot use a fully dynamic import statement
@ -37,6 +38,7 @@ const routeWrapper = (routeNodes: RouteNode[], root: RouteNode[]) => {
)}
</Suspense>
);
rn.errorElement = <RouteErrorBoundary />;
}
root.push(rn);
const children = Array.isArray(rn.children) ? rn.children : null;

View File

@ -8,7 +8,10 @@ export const usePersonalInfoByName = (username: string) => {
const apiUrl = '/answer/api/v1/personal/user/info';
const { data, error, mutate } = useSWR<Type.UserInfoRes, Error>(
username ? `${apiUrl}?username=${username}` : null,
request.instance.get,
(url) =>
request.get(url, {
allow404: true,
}),
);
return {
data,

View File

@ -47,7 +47,9 @@ export const useTagInfo = ({ id = '', name = '' }) => {
name = encodeURIComponent(name);
apiUrl = `/answer/api/v1/tag?name=${name}`;
}
const { data, error } = useSWR<Type.TagInfo>(apiUrl, request.instance.get);
const { data, error } = useSWR<Type.TagInfo>(apiUrl, (url) =>
request.get(url, { allow404: true }),
);
return {
data,
isLoading: !data && !error,

View File

@ -171,6 +171,7 @@ export const saveQuestion = (params: Type.QuestionParams) => {
export const questionDetail = (id: string) => {
return request.get<Type.QuestionDetailRes>(
`/answer/api/v1/question/info?id=${id}`,
{ allow404: true },
);
};

View File

@ -10,6 +10,7 @@ import pageTagStore from './pageTags';
import customizeStore from './customize';
import themeSettingStore from './themeSetting';
import loginToContinueStore from './loginToContinue';
import notFoundStore from './notFound';
export {
toastStore,
@ -23,4 +24,5 @@ export {
themeSettingStore,
seoSettingStore,
loginToContinueStore,
notFoundStore,
};

23
ui/src/stores/notFound.ts Normal file
View File

@ -0,0 +1,23 @@
import create from 'zustand';
interface NotFoundType {
visible: boolean;
show: () => void;
hide: () => void;
}
const notFound = create<NotFoundType>((set) => ({
visible: false,
show: () => {
set(() => {
return { visible: true };
});
},
hide: () => {
set(() => {
return { visible: false };
});
},
}));
export default notFound;

View File

@ -237,7 +237,26 @@ function htmlToReact(html: string) {
const cleanedHtml = DOMPurify.sanitize(html, {
USE_PROFILES: { html: true },
});
return parse(cleanedHtml);
const ele = document.createElement('div');
ele.innerHTML = cleanedHtml;
ele.querySelectorAll('table').forEach((table) => {
if (
(!table || (table.parentNode as HTMLDivElement))?.classList.contains(
'table-responsive',
)
) {
return;
}
table.classList.add('table', 'table-bordered');
const div = document.createElement('div');
div.className = 'table-responsive';
table.parentNode?.replaceChild(div, table);
div.appendChild(table);
});
return parse(ele.innerHTML);
}
export {

View File

@ -63,7 +63,7 @@ export const deriveLoginState = (): TLoginState => {
return ls;
};
const isIgnoredPath = (ignoredPath: string | string[]) => {
export const isIgnoredPath = (ignoredPath: string | string[]) => {
if (!Array.isArray(ignoredPath)) {
ignoredPath = [ignoredPath];
}

View File

@ -1,6 +1,8 @@
export { default as request } from './request';
export { default as Storage } from './storage';
export { floppyNavigation } from './floppyNavigation';
export { default as storageExpires } from './storageWithExpires';
export { default as SaveDraft } from './saveDraft';
export * as guard from './guard';
export * as localize from './localize';

View File

@ -2,19 +2,24 @@ import axios, { AxiosResponse } from 'axios';
import type { AxiosInstance, AxiosRequestConfig, AxiosError } from 'axios';
import { Modal } from '@/components';
import { loggedUserInfoStore, toastStore } from '@/stores';
import { LOGGED_TOKEN_STORAGE_KEY } from '@/common/constants';
import { loggedUserInfoStore, toastStore, notFoundStore } from '@/stores';
import { LOGGED_TOKEN_STORAGE_KEY, IGNORE_PATH_LIST } from '@/common/constants';
import { RouteAlias } from '@/router/alias';
import { getCurrentLang } from '@/utils/localize';
import Storage from './storage';
import { floppyNavigation } from './floppyNavigation';
import { isIgnoredPath } from './guard';
const baseConfig = {
timeout: 10000,
withCredentials: true,
};
interface APIconfig extends AxiosRequestConfig {
allow404: boolean;
}
class Request {
instance: AxiosInstance;
@ -49,6 +54,9 @@ class Request {
(error) => {
const { status, data: respData } = error.response || {};
const { data = {}, msg = '', reason = '' } = respData || {};
console.log('response error:', error);
if (status === 400) {
// show error message
if (data instanceof Object && data.err_type) {
@ -99,12 +107,14 @@ class Request {
// 401: Re-login required
if (status === 401) {
// clear userinfo
notFoundStore.getState().hide();
loggedUserInfoStore.getState().clear();
floppyNavigation.navigateToLogin();
return Promise.reject(false);
}
if (status === 403) {
// Permission interception
notFoundStore.getState().hide();
if (data?.type === 'url_expired') {
// url expired
floppyNavigation.navigate(RouteAlias.activationFailed, () => {
@ -135,6 +145,14 @@ class Request {
}
return Promise.reject(false);
}
if (status === 404 && error.config?.allow404) {
if (isIgnoredPath(IGNORE_PATH_LIST)) {
return Promise.reject(false);
}
notFoundStore.getState().show();
return Promise.reject(false);
}
if (status >= 500) {
console.error(
`Request failed with status code ${status}, ${msg || ''}`,
@ -149,7 +167,7 @@ class Request {
return this.instance.request(config);
}
public get<T = any>(url: string, config?: AxiosRequestConfig): Promise<T> {
public get<T = any>(url: string, config?: APIconfig): Promise<T> {
return this.instance.get(url, config);
}

87
ui/src/utils/saveDraft.ts Normal file
View File

@ -0,0 +1,87 @@
import { debounce } from 'lodash';
import {
DRAFT_QUESTION_STORAGE_KEY,
DRAFT_ANSWER_STORAGE_KEY,
} from '@/common/constants';
import { storageExpires as storage } from '@/utils';
export type QuestionDraft = {
params: {
title: string;
content: string;
tags: any[];
answer: string;
};
callback?: () => void;
};
export type AnswerDraft = {
questionId: string;
content: string;
callback?: () => void;
};
type DraftType = {
type: 'question' | 'answer';
};
export type DraftParams = QuestionDraft | AnswerDraft;
class SaveDraft {
type: DraftType['type'];
status: 'save' | 'remove';
constructor({ type = 'question' }: DraftType) {
this.type = type;
this.status = 'save';
}
save = debounce((data: DraftParams) => {
// TODO
if (this.status === 'remove') {
return;
}
if (this.type === 'question') {
const { params, callback } = data as QuestionDraft;
this.storeDraft(params, callback);
}
if (this.type === 'answer') {
const { content, questionId, callback } = data as AnswerDraft;
if (!questionId || !content) {
return;
}
this.storeDraft({ content, questionId }, callback);
}
}, 3000);
remove() {
this.status = 'remove';
const that = this;
if (this.type === 'question') {
storage.remove(DRAFT_QUESTION_STORAGE_KEY, () => {
that.status = 'save';
});
}
if (this.type === 'answer') {
storage.remove(DRAFT_ANSWER_STORAGE_KEY, () => {
that.status = 'save';
});
}
}
private storeDraft = (params: any, callback) => {
const key =
this.type === 'question'
? DRAFT_QUESTION_STORAGE_KEY
: DRAFT_ANSWER_STORAGE_KEY;
storage.set(key, params);
callback?.();
};
}
export default SaveDraft;

View File

@ -0,0 +1,50 @@
import { DRAFT_TIMESIGH_STORAGE_KEY as timeSign } from '@/common/constants';
const store = {
storage: localStorage || window.localStorage,
set(key: string, value, time?: number): void {
const t = time || Date.now() + 1000 * 60 * 60 * 24 * 7; // default 7 days
try {
this.storage.setItem(key, `${t}${timeSign}${JSON.stringify(value)}`);
} catch {
// ignore
console.error('set storage error: the key is', key);
}
},
get(key: string): any {
const timeSignLen = timeSign.length;
let index = 0;
let time = 0;
let res;
try {
res = this.storage.getItem(key);
} catch {
console.error('get storage error: the key is', key);
}
if (res) {
index = res.indexOf(timeSign);
time = +res.slice(0, index);
if (time > new Date().getTime()) {
res = res.slice(index + timeSignLen);
try {
res = JSON.parse(res);
} catch {
// ignore
}
} else {
// timeout remove storage
res = null;
this.storage.removeItem(key);
}
return res;
}
return res;
},
remove(key: string, callback?: () => void): void {
this.storage.removeItem(key);
callback?.();
},
};
export default store;

View File

@ -3,28 +3,28 @@
<div class="justify-content-center row">
<div class="col-xxl-7 col-lg-8 col-sm-12">
<div class="d-flex flex-column flex-md-row mb-4">
<a href="/users/{{.userinfo.Info.Username}}"><img
src="{{.userinfo.Info.Avatar}}"
<a href="/users/{{.userinfo.Username}}"><img
src="{{.userinfo.Avatar}}"
width="160px" height="160px" class="rounded" alt="" /></a>
<div class="ms-0 ms-md-4 mt-4 mt-md-0">
<div class="d-flex align-items-center mb-2">
<a class="link-dark h3 mb-0" href="/users/{{.userinfo.Info.Username}}">{{.userinfo.Info.DisplayName}}</a>
<a class="link-dark h3 mb-0" href="/users/{{.userinfo.Username}}">{{.userinfo.DisplayName}}</a>
</div>
<div class="text-secondary mb-4">@{{.userinfo.Info.Username}}</div>
<div class="text-secondary mb-4">@{{.userinfo.Username}}</div>
<div class="d-flex flex-wrap mb-3">
<div class="me-3">
<strong class="fs-5">{{.userinfo.Info.Rank}}</strong><span class="text-secondary"> {{translator $.language "ui.personal.x_reputation"}}</span>
<strong class="fs-5">{{.userinfo.Rank}}</strong><span class="text-secondary"> {{translator $.language "ui.personal.x_reputation"}}</span>
</div>
<div class="me-3">
<strong class="fs-5">{{.userinfo.Info.AnswerCount}}</strong><span class="text-secondary"> {{translator $.language "ui.personal.x_answers"}}</span>
<strong class="fs-5">{{.userinfo.AnswerCount}}</strong><span class="text-secondary"> {{translator $.language "ui.personal.x_answers"}}</span>
</div>
<div>
<strong class="fs-5">{{.userinfo.Info.QuestionCount}}</strong><span class="text-secondary"> {{translator $.language "ui.personal.x_questions"}}</span>
<strong class="fs-5">{{.userinfo.QuestionCount}}</strong><span class="text-secondary"> {{translator $.language "ui.personal.x_questions"}}</span>
</div>
</div>
{{if .userinfo.Info.Website }}
<div class="d-flex align-items-center"><i class="br bi-house-door-fill me-2"></i><a class="link-secondary" href="{{.userinfo.Info.Website}}">{{.userinfo.Info.Website}}</a></div>
{{if .userinfo.Website }}
<div class="d-flex align-items-center"><i class="br bi-house-door-fill me-2"></i><a class="link-secondary" href="{{.userinfo.Website}}">{{.userinfo.Website}}</a></div>
{{else}}
{{end}}
<div class="d-flex text-secondary"></div>