mirror of https://gitee.com/answerdev/answer.git
Merge branch 'feat/1.1.2/user-center' of github.com:answerdev/answer into feat/1.1.2/user-center
This commit is contained in:
commit
bed55985dd
86
docs/docs.go
86
docs/docs.go
|
@ -3654,6 +3654,45 @@ const docTemplate = `{
|
|||
}
|
||||
}
|
||||
},
|
||||
"/answer/api/v1/question/operation": {
|
||||
"put": {
|
||||
"security": [
|
||||
{
|
||||
"ApiKeyAuth": []
|
||||
}
|
||||
],
|
||||
"description": "Operation question \\n operation [pin unpin hide show]",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Question"
|
||||
],
|
||||
"summary": "Operation question",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "question",
|
||||
"name": "data",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/schema.OperationQuestionReq"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/handler.RespBody"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/answer/api/v1/question/page": {
|
||||
"get": {
|
||||
"description": "get questions by page",
|
||||
|
@ -7442,6 +7481,21 @@ const docTemplate = `{
|
|||
}
|
||||
}
|
||||
},
|
||||
"schema.OperationQuestionReq": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"id"
|
||||
],
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"operation": {
|
||||
"description": "operation [pin unpin hide show]",
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"schema.PermissionMemberAction": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
@ -7615,6 +7669,14 @@ const docTemplate = `{
|
|||
"operator": {
|
||||
"$ref": "#/definitions/schema.QuestionPageRespOperator"
|
||||
},
|
||||
"pin": {
|
||||
"description": "1: unpin, 2: pin",
|
||||
"type": "integer"
|
||||
},
|
||||
"show": {
|
||||
"description": "0: show, 1: hide",
|
||||
"type": "integer"
|
||||
},
|
||||
"status": {
|
||||
"type": "integer"
|
||||
},
|
||||
|
@ -7923,10 +7985,6 @@ const docTemplate = `{
|
|||
"custom_header": {
|
||||
"type": "string",
|
||||
"maxLength": 65536
|
||||
},
|
||||
"custom_sidebar": {
|
||||
"type": "string",
|
||||
"maxLength": 65536
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -7948,10 +8006,6 @@ const docTemplate = `{
|
|||
"custom_header": {
|
||||
"type": "string",
|
||||
"maxLength": 65536
|
||||
},
|
||||
"custom_sidebar": {
|
||||
"type": "string",
|
||||
"maxLength": 65536
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -8053,18 +8107,10 @@ const docTemplate = `{
|
|||
"schema.SiteInterfaceReq": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"default_avatar",
|
||||
"language",
|
||||
"time_zone"
|
||||
],
|
||||
"properties": {
|
||||
"default_avatar": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"system",
|
||||
"gravatar"
|
||||
]
|
||||
},
|
||||
"language": {
|
||||
"type": "string",
|
||||
"maxLength": 128
|
||||
|
@ -8078,18 +8124,10 @@ const docTemplate = `{
|
|||
"schema.SiteInterfaceResp": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"default_avatar",
|
||||
"language",
|
||||
"time_zone"
|
||||
],
|
||||
"properties": {
|
||||
"default_avatar": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"system",
|
||||
"gravatar"
|
||||
]
|
||||
},
|
||||
"language": {
|
||||
"type": "string",
|
||||
"maxLength": 128
|
||||
|
|
|
@ -3642,6 +3642,45 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"/answer/api/v1/question/operation": {
|
||||
"put": {
|
||||
"security": [
|
||||
{
|
||||
"ApiKeyAuth": []
|
||||
}
|
||||
],
|
||||
"description": "Operation question \\n operation [pin unpin hide show]",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Question"
|
||||
],
|
||||
"summary": "Operation question",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "question",
|
||||
"name": "data",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/schema.OperationQuestionReq"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/handler.RespBody"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/answer/api/v1/question/page": {
|
||||
"get": {
|
||||
"description": "get questions by page",
|
||||
|
@ -7430,6 +7469,21 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"schema.OperationQuestionReq": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"id"
|
||||
],
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"operation": {
|
||||
"description": "operation [pin unpin hide show]",
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"schema.PermissionMemberAction": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
@ -7603,6 +7657,14 @@
|
|||
"operator": {
|
||||
"$ref": "#/definitions/schema.QuestionPageRespOperator"
|
||||
},
|
||||
"pin": {
|
||||
"description": "1: unpin, 2: pin",
|
||||
"type": "integer"
|
||||
},
|
||||
"show": {
|
||||
"description": "0: show, 1: hide",
|
||||
"type": "integer"
|
||||
},
|
||||
"status": {
|
||||
"type": "integer"
|
||||
},
|
||||
|
@ -7911,10 +7973,6 @@
|
|||
"custom_header": {
|
||||
"type": "string",
|
||||
"maxLength": 65536
|
||||
},
|
||||
"custom_sidebar": {
|
||||
"type": "string",
|
||||
"maxLength": 65536
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -7936,10 +7994,6 @@
|
|||
"custom_header": {
|
||||
"type": "string",
|
||||
"maxLength": 65536
|
||||
},
|
||||
"custom_sidebar": {
|
||||
"type": "string",
|
||||
"maxLength": 65536
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -8041,18 +8095,10 @@
|
|||
"schema.SiteInterfaceReq": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"default_avatar",
|
||||
"language",
|
||||
"time_zone"
|
||||
],
|
||||
"properties": {
|
||||
"default_avatar": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"system",
|
||||
"gravatar"
|
||||
]
|
||||
},
|
||||
"language": {
|
||||
"type": "string",
|
||||
"maxLength": 128
|
||||
|
@ -8066,18 +8112,10 @@
|
|||
"schema.SiteInterfaceResp": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"default_avatar",
|
||||
"language",
|
||||
"time_zone"
|
||||
],
|
||||
"properties": {
|
||||
"default_avatar": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"system",
|
||||
"gravatar"
|
||||
]
|
||||
},
|
||||
"language": {
|
||||
"type": "string",
|
||||
"maxLength": 128
|
||||
|
|
|
@ -1128,6 +1128,16 @@ definitions:
|
|||
description: inbox achievement
|
||||
type: string
|
||||
type: object
|
||||
schema.OperationQuestionReq:
|
||||
properties:
|
||||
id:
|
||||
type: string
|
||||
operation:
|
||||
description: operation [pin unpin hide show]
|
||||
type: string
|
||||
required:
|
||||
- id
|
||||
type: object
|
||||
schema.PermissionMemberAction:
|
||||
properties:
|
||||
action:
|
||||
|
@ -1252,6 +1262,12 @@ definitions:
|
|||
type: string
|
||||
operator:
|
||||
$ref: '#/definitions/schema.QuestionPageRespOperator'
|
||||
pin:
|
||||
description: '1: unpin, 2: pin'
|
||||
type: integer
|
||||
show:
|
||||
description: '0: show, 1: hide'
|
||||
type: integer
|
||||
status:
|
||||
type: integer
|
||||
tags:
|
||||
|
@ -1466,9 +1482,6 @@ definitions:
|
|||
custom_header:
|
||||
maxLength: 65536
|
||||
type: string
|
||||
custom_sidebar:
|
||||
maxLength: 65536
|
||||
type: string
|
||||
type: object
|
||||
schema.SiteCustomCssHTMLResp:
|
||||
properties:
|
||||
|
@ -1484,9 +1497,6 @@ definitions:
|
|||
custom_header:
|
||||
maxLength: 65536
|
||||
type: string
|
||||
custom_sidebar:
|
||||
maxLength: 65536
|
||||
type: string
|
||||
type: object
|
||||
schema.SiteGeneralReq:
|
||||
properties:
|
||||
|
@ -1557,11 +1567,6 @@ definitions:
|
|||
type: object
|
||||
schema.SiteInterfaceReq:
|
||||
properties:
|
||||
default_avatar:
|
||||
enum:
|
||||
- system
|
||||
- gravatar
|
||||
type: string
|
||||
language:
|
||||
maxLength: 128
|
||||
type: string
|
||||
|
@ -1569,17 +1574,11 @@ definitions:
|
|||
maxLength: 128
|
||||
type: string
|
||||
required:
|
||||
- default_avatar
|
||||
- language
|
||||
- time_zone
|
||||
type: object
|
||||
schema.SiteInterfaceResp:
|
||||
properties:
|
||||
default_avatar:
|
||||
enum:
|
||||
- system
|
||||
- gravatar
|
||||
type: string
|
||||
language:
|
||||
maxLength: 128
|
||||
type: string
|
||||
|
@ -1587,7 +1586,6 @@ definitions:
|
|||
maxLength: 128
|
||||
type: string
|
||||
required:
|
||||
- default_avatar
|
||||
- language
|
||||
- time_zone
|
||||
type: object
|
||||
|
@ -4426,6 +4424,30 @@ paths:
|
|||
summary: get question details
|
||||
tags:
|
||||
- Question
|
||||
/answer/api/v1/question/operation:
|
||||
put:
|
||||
consumes:
|
||||
- application/json
|
||||
description: Operation question \n operation [pin unpin hide show]
|
||||
parameters:
|
||||
- description: question
|
||||
in: body
|
||||
name: data
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/schema.OperationQuestionReq'
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/handler.RespBody'
|
||||
security:
|
||||
- ApiKeyAuth: []
|
||||
summary: Operation question
|
||||
tags:
|
||||
- Question
|
||||
/answer/api/v1/question/page:
|
||||
get:
|
||||
consumes:
|
||||
|
|
|
@ -22,6 +22,14 @@ backend:
|
|||
other: Close
|
||||
reopen:
|
||||
other: Reopen
|
||||
pin:
|
||||
other: Pin
|
||||
hide:
|
||||
other: Unlist
|
||||
unpin:
|
||||
other: Unpin
|
||||
show:
|
||||
other: List
|
||||
role:
|
||||
name:
|
||||
user:
|
||||
|
@ -29,7 +37,7 @@ backend:
|
|||
admin:
|
||||
other: Gweinyddwr
|
||||
moderator:
|
||||
other: Aroglygydd
|
||||
other: Cymedrolwr
|
||||
description:
|
||||
user:
|
||||
other: Diofyn heb unrhyw fynediad arbennig.
|
||||
|
@ -53,7 +61,7 @@ backend:
|
|||
other: Nid yw e-bost a chyfrinair yn cyfateb.
|
||||
answer:
|
||||
not_found:
|
||||
other: Ateb heb ei ddarganfod.
|
||||
other: Ni cheir yr ateb.
|
||||
cannot_deleted:
|
||||
other: Dim caniatâd i ddileu.
|
||||
cannot_update:
|
||||
|
@ -119,7 +127,7 @@ backend:
|
|||
other: Heb ganfod yr adroddiad.
|
||||
tag:
|
||||
already_exist:
|
||||
other: Tag yn bodoli eisoes.
|
||||
other: Mae tag eisoes yn bodoli.
|
||||
not_found:
|
||||
other: Tag heb ei ddarganfod.
|
||||
recommend_tag_not_found:
|
||||
|
@ -158,7 +166,7 @@ backend:
|
|||
username_duplicate:
|
||||
other: Cymerwyd yr enw defnyddiwr eisoes.
|
||||
set_avatar:
|
||||
other: Methodd gwneud avatar.
|
||||
other: Methodd set avatar.
|
||||
cannot_update_your_role:
|
||||
other: Ni allwch addasu eich rôl.
|
||||
not_allowed_registration:
|
||||
|
@ -176,7 +184,7 @@ backend:
|
|||
other: Methu creu'r ffeil config.yaml.
|
||||
upload:
|
||||
unsupported_file_format:
|
||||
other: Fformat ffeil anghydnaws.
|
||||
other: Fformat ffeil heb ei gefnogi.
|
||||
report:
|
||||
spam:
|
||||
name:
|
||||
|
@ -200,7 +208,7 @@ backend:
|
|||
other: Postiwyd hwn fel ateb, ond nid yw'n ceisio ateb y cwestiwn. Mae'n bosibl y dylai fod yn olygiad, yn sylwad, yn gwestiwn arall, neu'n cael ei ddileu yn gyfan gwbl.
|
||||
not_need:
|
||||
name:
|
||||
other: nad oes ei angen yn pellach
|
||||
other: nad oes ei angen mwyach
|
||||
desc:
|
||||
other: Mae'r sylw hwn yn hen ffasiwn, yn sgyrsiol neu ddim yn berthnasol i'r post hwn.
|
||||
other:
|
||||
|
@ -268,7 +276,7 @@ ui:
|
|||
how_to_format:
|
||||
title: Sut i Fformatio
|
||||
desc: >-
|
||||
<ul class="mb-0"><li><p class="mb-2">i wneud cysylltiadau</p><pre class="mb-2"><code><https://url.com><br/><br/>[Title](https://url.com)</code></pre></li><li><p class="mb-2">rhoi toriad llinell rhwng paragraffau</p></li><li><p class="mb-2"><em>_italig_</em> or **<strong>bold</strong>**</p></li><li><p class="mb-2">cod mewnoliad gan 4 bylchau</p></li><li><p class="mb-2">dyfyniad trwy osod <code>></code> ar ddechrau'r llinell</p></li><li><p class="mb-2">backtic yn dianc<code>`fel_hyn_`</code></p></li><li><p class="mb-2">creu ffensys cod gyda backticks <code>`</code></p><pre class="mb-0"><code>```<br/>cod yma<br/>```</code></pre></li></ul>
|
||||
<ul class="mb-0"><li><p class="mb-2">i wneud cysylltiadau</p><pre class="mb-2"><code><https://url.com><br/><br/>[Title]( https://url.com)</code></pre></li><li><p class="mb-2">rhoi dychweliadau rhwng paragraffau</p></li><li><p class="mb-2"><em>_italic_</em> neu **<strong>beiddgar</strong>**</p></li><li><p class="mb-2">cod mewnoliad gan 4 bwlch</p></li><li><p class="mb-2">dyfyniad drwy osod <code>></code> ar ddechrau'r llinell</p></li><li><p class="mb-2">backtick yn dianc o <code>`like _this_`</code></p></li><li><p class="mb-2">creu ffensys cod gyda thic wrth gefn <code>`</code></p><pre class="mb-0"><code>```<br/>cod yma<br/>```</code></pre></li></ul>
|
||||
pagination:
|
||||
prev: Cynt
|
||||
next: Nesaf
|
||||
|
@ -277,7 +285,7 @@ ui:
|
|||
questions: Cwestiynau
|
||||
tag: Tag
|
||||
tags: Tagiau
|
||||
tag_wiki: wici tag
|
||||
tag_wiki: tag wiki
|
||||
create_tag: Creu Tag
|
||||
edit_tag: Golygu Tag
|
||||
ask_a_question: Ychwanegu Cwestiwn
|
||||
|
@ -312,7 +320,7 @@ ui:
|
|||
title: Mae'ch Cyfrif wedi'i Atal
|
||||
until_time: "Cafodd eich cyfrif ei atal tan {{ time }}."
|
||||
forever: Cafodd y defnyddiwr hwn ei atal am byth.
|
||||
end: Nid ydych yn bodloni canllaw cymunedol.
|
||||
end: Nid ydych yn arwain cymunedol.
|
||||
editor:
|
||||
blockquote:
|
||||
text: Dyfyniad
|
||||
|
@ -359,7 +367,7 @@ ui:
|
|||
help:
|
||||
text: Cymorth
|
||||
hr:
|
||||
text: Llinell lorweddol
|
||||
text: Rheol Llorweddol
|
||||
image:
|
||||
text: Delwedd
|
||||
add_image: Ychwanegu delwedd
|
||||
|
@ -759,6 +767,7 @@ ui:
|
|||
btn: Add question
|
||||
answers: answers
|
||||
question_detail:
|
||||
action: Action
|
||||
Asked: Asked
|
||||
asked: asked
|
||||
update: Modified
|
||||
|
@ -800,7 +809,10 @@ ui:
|
|||
confirm_btn: Reopen
|
||||
title: Reopen this post
|
||||
content: Are you sure you want to reopen?
|
||||
success: This post has been reopened
|
||||
pin:
|
||||
title: Pin this post
|
||||
content: Are you sure you wish to pinned globally? This post will appear at the top of all post lists.
|
||||
confirm_btn: Pin
|
||||
delete:
|
||||
title: Delete this post
|
||||
question: >-
|
||||
|
@ -808,7 +820,6 @@ ui:
|
|||
answer_accepted: >-
|
||||
<p>We do not recommend <strong>deleting accepted answer</strong> because doing so deprives future readers of this knowledge. </p> Repeated deletion of accepted answers can result in your account being blocked from answering. Are you sure you wish to delete?
|
||||
other: Are you sure you wish to delete?
|
||||
tip_question_deleted: This post has been deleted
|
||||
tip_answer_deleted: This answer has been deleted
|
||||
btns:
|
||||
confirm: Confirm
|
||||
|
@ -824,6 +835,7 @@ ui:
|
|||
reject: Reject
|
||||
skip: Skip
|
||||
discard_draft: Discard draft
|
||||
pinned: Pinned
|
||||
search:
|
||||
title: Search Results
|
||||
keywords: Keywords
|
||||
|
@ -1385,6 +1397,10 @@ ui:
|
|||
closed: closed
|
||||
reopened: reopened
|
||||
created: created
|
||||
pin: pinned
|
||||
unpin: unpinned
|
||||
show: listed
|
||||
hide: unlisted
|
||||
title: "History for"
|
||||
tag_title: "Timeline for"
|
||||
show_votes: "Show votes"
|
||||
|
@ -1411,4 +1427,8 @@ ui:
|
|||
discard_confirm: Are you sure you want to discard your draft?
|
||||
messages:
|
||||
post_deleted: This post has been deleted.
|
||||
|
||||
post_pin: This post has been pinned.
|
||||
post_unpin: This post has been unpinned.
|
||||
post_hide_list: This post has been hidden from list.
|
||||
post_show_list: This post has been shown to list.
|
||||
post_reopen: This post has been reopened.
|
||||
|
|
504
i18n/de_DE.yaml
504
i18n/de_DE.yaml
|
@ -22,6 +22,14 @@ backend:
|
|||
other: Close
|
||||
reopen:
|
||||
other: Reopen
|
||||
pin:
|
||||
other: Pin
|
||||
hide:
|
||||
other: Unlist
|
||||
unpin:
|
||||
other: Unpin
|
||||
show:
|
||||
other: List
|
||||
role:
|
||||
name:
|
||||
user:
|
||||
|
@ -759,6 +767,7 @@ ui:
|
|||
btn: Frage hinzufügen
|
||||
answers: antworten
|
||||
question_detail:
|
||||
action: Action
|
||||
Asked: Fragte
|
||||
asked: fragte
|
||||
update: Geändert
|
||||
|
@ -800,7 +809,10 @@ ui:
|
|||
confirm_btn: Reopen
|
||||
title: Diesen Beitrag erneut öffnen
|
||||
content: Möchten Sie wirklich wieder öffnen?
|
||||
success: Dieser Beitrag wurde wieder geöffnet
|
||||
pin:
|
||||
title: Pin this post
|
||||
content: Are you sure you wish to pinned globally? This post will appear at the top of all post lists.
|
||||
confirm_btn: Pin
|
||||
delete:
|
||||
title: Diesen Beitrag löschen
|
||||
question: >-
|
||||
|
@ -808,7 +820,6 @@ ui:
|
|||
answer_accepted: >-
|
||||
<p>Wir empfehlen nicht, <strong>akzeptierte Antworten zu löschen</strong>, da dies zukünftigen Lesern dieses Wissen entzieht. </p> Das wiederholte Löschen akzeptierter Antworten kann dazu führen, dass Ihr Konto für das Antworten gesperrt wird. Möchten Sie wirklich löschen?
|
||||
other: Sind Sie sicher, dass Sie löschen möchten?
|
||||
tip_question_deleted: Dieser Beitrag wurde gelöscht
|
||||
tip_answer_deleted: Diese Antwort wurde gelöscht
|
||||
btns:
|
||||
confirm: Bestätigen
|
||||
|
@ -824,6 +835,7 @@ ui:
|
|||
reject: Ablehnen
|
||||
skip: Überspringen
|
||||
discard_draft: Entwurf verwerfen
|
||||
pinned: Pinned
|
||||
search:
|
||||
title: Suchergebnisse
|
||||
keywords: Schlüsselwörter
|
||||
|
@ -901,63 +913,63 @@ ui:
|
|||
question: frage
|
||||
bookmarks: Lesezeichen
|
||||
reputation: Ruf
|
||||
comments: Comments
|
||||
votes: Votes
|
||||
newest: Newest
|
||||
score: Score
|
||||
edit_profile: Edit Profile
|
||||
visited_x_days: "Visited {{ count }} days"
|
||||
viewed: Viewed
|
||||
joined: Joined
|
||||
last_login: Seen
|
||||
about_me: About Me
|
||||
about_me_empty: "// Hello, World !"
|
||||
top_answers: Top Answers
|
||||
top_questions: Top Questions
|
||||
stats: Stats
|
||||
list_empty: No posts found.<br />Perhaps you'd like to select a different tab?
|
||||
accepted: Accepted
|
||||
answered: answered
|
||||
asked: asked
|
||||
upvote: upvote
|
||||
downvote: downvote
|
||||
comments: Kommentare
|
||||
votes: Stimmen
|
||||
newest: Neueste
|
||||
score: Punktzahl
|
||||
edit_profile: Profil bearbeiten
|
||||
visited_x_days: "{{ count }} Tage besucht"
|
||||
viewed: Gesehen
|
||||
joined: Beigetreten
|
||||
last_login: Gesehen
|
||||
about_me: Über mich
|
||||
about_me_empty: "// Hallo Welt !"
|
||||
top_answers: Top-Antworten
|
||||
top_questions: Top-Fragen
|
||||
stats: Statistiken
|
||||
list_empty: Keine Beiträge gefunden.<br />Vielleicht möchten Sie einen anderen Tab auswählen?
|
||||
accepted: Akzeptiert
|
||||
answered: antwortete
|
||||
asked: fragte
|
||||
upvote: positiv bewerten
|
||||
downvote: ablehnen
|
||||
mod_short: Mod
|
||||
mod_long: Moderators
|
||||
x_reputation: reputation
|
||||
x_votes: votes received
|
||||
x_answers: answers
|
||||
x_questions: questions
|
||||
mod_long: Moderatoren
|
||||
x_reputation: ruf
|
||||
x_votes: stimmen erhalten
|
||||
x_answers: antworten
|
||||
x_questions: fragen
|
||||
install:
|
||||
title: Answer
|
||||
next: Next
|
||||
done: Done
|
||||
config_yaml_error: Can't create the config.yaml file.
|
||||
title: Antwort
|
||||
next: Nächste
|
||||
done: Erledigt
|
||||
config_yaml_error: Die Datei config.yaml kann nicht erstellt werden.
|
||||
lang:
|
||||
label: Please Choose a Language
|
||||
label: Bitte wählen Sie eine Sprache
|
||||
db_type:
|
||||
label: Database Engine
|
||||
label: Datenbank-Engine
|
||||
db_username:
|
||||
label: Username
|
||||
placeholder: root
|
||||
msg: Username cannot be empty.
|
||||
label: Nutzername
|
||||
placeholder: wurzel
|
||||
msg: Benutzername darf nicht leer sein.
|
||||
db_password:
|
||||
label: Passwort
|
||||
placeholder: root
|
||||
msg: Password cannot be empty.
|
||||
placeholder: wurzel
|
||||
msg: Passwort kann nicht leer sein.
|
||||
db_host:
|
||||
label: Database Host
|
||||
label: Datenbank-Host
|
||||
placeholder: "db:3306"
|
||||
msg: Database Host cannot be empty.
|
||||
msg: Datenbankhost darf nicht leer sein.
|
||||
db_name:
|
||||
label: Database Name
|
||||
label: Name der Datenbank
|
||||
placeholder: answer
|
||||
msg: Database Name cannot be empty.
|
||||
msg: Der Datenbankname darf nicht leer sein.
|
||||
db_file:
|
||||
label: Database File
|
||||
placeholder: /data/answer.db
|
||||
msg: Database File cannot be empty.
|
||||
label: Datenbankdatei
|
||||
placeholder: /data/answer.Weder noch
|
||||
msg: Datenbankdatei darf nicht leer sein.
|
||||
config_yaml:
|
||||
title: Create config.yaml
|
||||
title: Erstellen Sie config.yaml
|
||||
label: Die erstellte config.yaml-Datei.
|
||||
desc: >-
|
||||
Sie können die <1>config.yaml</1> Datei manuell im <1>/var/wwww/xxx/</1> Verzeichnis erstellen und den folgenden Text einfügen.
|
||||
|
@ -1066,18 +1078,18 @@ ui:
|
|||
answer_links: Antwortlinks
|
||||
documents: Unterlagen
|
||||
feedback: Rückmeldung
|
||||
support: Support
|
||||
review: Review
|
||||
config: Config
|
||||
update_to: Update to
|
||||
latest: Latest
|
||||
check_failed: Check failed
|
||||
support: Unterstützung
|
||||
review: Rezension
|
||||
config: Konfig
|
||||
update_to: Aktualisieren zu
|
||||
latest: Neueste
|
||||
check_failed: Prüfung fehlgeschlagen
|
||||
"yes": "Ja"
|
||||
"no": "Nein"
|
||||
not_allowed: Not allowed
|
||||
allowed: Allowed
|
||||
enabled: Enabled
|
||||
disabled: Disabled
|
||||
not_allowed: Nicht erlaubt
|
||||
allowed: Erlaubt
|
||||
enabled: Ermöglicht
|
||||
disabled: Behinderte
|
||||
flags:
|
||||
title: Flaggen
|
||||
pending: Ausstehend
|
||||
|
@ -1133,282 +1145,290 @@ ui:
|
|||
all: Alle
|
||||
staff: Mitarbeiter
|
||||
inactive: Inaktiv
|
||||
suspended: Suspendiert
|
||||
deleted: Deleted
|
||||
suspended: Ausgesetzt
|
||||
deleted: Gelöscht
|
||||
normal: Normal
|
||||
Moderator: Moderator
|
||||
Admin: Admin
|
||||
User: User
|
||||
Moderator: Moderation
|
||||
Admin: Administrator
|
||||
User: Benutzer
|
||||
filter:
|
||||
placeholder: "Filter by name, user:id"
|
||||
set_new_password: Set new password
|
||||
change_status: Change status
|
||||
change_role: Change role
|
||||
show_logs: Show logs
|
||||
add_user: Add user
|
||||
placeholder: "Filtern Sie nach Name, Benutzer:Id"
|
||||
set_new_password: Neues Passwort festlegen
|
||||
change_status: Status ändern
|
||||
change_role: Rolle wechseln
|
||||
show_logs: Protokolle anzeigen
|
||||
add_user: Benutzer hinzufügen
|
||||
new_password_modal:
|
||||
title: Set new password
|
||||
title: Neues Passwort festlegen
|
||||
form:
|
||||
fields:
|
||||
password:
|
||||
label: Password
|
||||
text: The user will be logged out and need to login again.
|
||||
msg: Password must be at 8-32 characters in length.
|
||||
btn_cancel: Cancel
|
||||
btn_submit: Submit
|
||||
label: Passwort
|
||||
text: Der Benutzer wird abgemeldet und muss sich erneut anmelden.
|
||||
msg: Das Passwort muss zwischen 8 und 32 Zeichen lang sein.
|
||||
btn_cancel: Stornieren
|
||||
btn_submit: Senden
|
||||
user_modal:
|
||||
title: Add new user
|
||||
title: Neuen Benutzer hinzufügen
|
||||
form:
|
||||
fields:
|
||||
display_name:
|
||||
label: Display Name
|
||||
msg: Display Name must be at 3-30 characters in length.
|
||||
label: Anzeigename
|
||||
msg: Der Anzeigename muss zwischen 3 und 30 Zeichen lang sein.
|
||||
email:
|
||||
label: Email
|
||||
msg: Email is not valid.
|
||||
msg: Email ist ungültig.
|
||||
password:
|
||||
label: Password
|
||||
msg: Password must be at 8-32 characters in length.
|
||||
btn_cancel: Cancel
|
||||
btn_submit: Submit
|
||||
label: Passwort
|
||||
msg: Das Passwort muss zwischen 8 und 32 Zeichen lang sein.
|
||||
btn_cancel: Stornieren
|
||||
btn_submit: Senden
|
||||
questions:
|
||||
page_title: Questions
|
||||
page_title: Fragen
|
||||
normal: Normal
|
||||
closed: Closed
|
||||
deleted: Deleted
|
||||
closed: Geschlossen
|
||||
deleted: Gelöscht
|
||||
post: Post
|
||||
votes: Votes
|
||||
answers: Answers
|
||||
created: Created
|
||||
votes: Stimmen
|
||||
answers: Antworten
|
||||
created: Erstellt
|
||||
status: Status
|
||||
action: Action
|
||||
change: Change
|
||||
action: Aktion
|
||||
change: Ändern
|
||||
filter:
|
||||
placeholder: "Filter by title, question:id"
|
||||
placeholder: "Filtern nach Titel, Frage:Id"
|
||||
answers:
|
||||
page_title: Answers
|
||||
page_title: Antworten
|
||||
normal: Normal
|
||||
deleted: Deleted
|
||||
deleted: Gelöscht
|
||||
post: Post
|
||||
votes: Votes
|
||||
created: Created
|
||||
votes: Stimmen
|
||||
created: Erstellt
|
||||
status: Status
|
||||
action: Action
|
||||
change: Change
|
||||
action: Aktion
|
||||
change: Ändern
|
||||
filter:
|
||||
placeholder: "Filter by title, answer:id"
|
||||
placeholder: "Filtern nach Titel, Antwort: id"
|
||||
general:
|
||||
page_title: General
|
||||
page_title: Allgemein
|
||||
name:
|
||||
label: Site Name
|
||||
msg: Site name cannot be empty.
|
||||
text: "The name of this site, as used in the title tag."
|
||||
label: Seitenname
|
||||
msg: Der Site-Name darf nicht leer sein.
|
||||
text: "Der Name dieser Website, wie er im Titel-Tag verwendet wird."
|
||||
site_url:
|
||||
label: Site URL
|
||||
msg: Site url cannot be empty.
|
||||
validate: Please enter a valid URL.
|
||||
text: The address of your site.
|
||||
label: Seiten-URL
|
||||
msg: Die Website-Url darf nicht leer sein.
|
||||
validate: Bitte geben Sie eine gültige URL ein.
|
||||
text: Die Adresse Ihrer Website.
|
||||
short_desc:
|
||||
label: Short Site Description
|
||||
msg: Short site description cannot be empty.
|
||||
text: "Short description, as used in the title tag on homepage."
|
||||
label: Kurze Seitenbeschreibung
|
||||
msg: Die kurze Website-Beschreibung darf nicht leer sein.
|
||||
text: "Kurze Beschreibung, wie im Titel-Tag auf der Homepage verwendet."
|
||||
desc:
|
||||
label: Site Description
|
||||
msg: Site description cannot be empty.
|
||||
text: "Describe this site in one sentence, as used in the meta description tag."
|
||||
label: Seitenbeschreibung
|
||||
msg: Die Websitebeschreibung darf nicht leer sein.
|
||||
text: "Beschreiben Sie diese Website in einem Satz, wie er im Meta-Beschreibungs-Tag verwendet wird."
|
||||
contact_email:
|
||||
label: Contact Email
|
||||
msg: Contact email cannot be empty.
|
||||
validate: Contact email is not valid.
|
||||
text: Email address of key contact responsible for this site.
|
||||
label: Kontakt E-mail
|
||||
msg: Kontakt-E-Mail darf nicht leer sein.
|
||||
validate: Kontakt-E-Mail ist ungültig.
|
||||
text: E-Mail-Adresse des Hauptkontakts, der für diese Website verantwortlich ist.
|
||||
interface:
|
||||
page_title: Interface
|
||||
page_title: Schnittstelle
|
||||
language:
|
||||
label: Interface Language
|
||||
msg: Interface language cannot be empty.
|
||||
text: User interface language. It will change when you refresh the page.
|
||||
label: Schnittstellensprache
|
||||
msg: Sprache der Benutzeroberfläche darf nicht leer sein.
|
||||
text: Sprache der Benutzeroberfläche. Sie ändert sich, wenn Sie die Seite aktualisieren.
|
||||
time_zone:
|
||||
label: Timezone
|
||||
msg: Timezone cannot be empty.
|
||||
text: Choose a city in the same timezone as you.
|
||||
label: Zeitzone
|
||||
msg: Die Zeitzone darf nicht leer sein.
|
||||
text: Wählen Sie eine Stadt in derselben Zeitzone wie Sie.
|
||||
avatar:
|
||||
label: Standard-Avatar
|
||||
text: Für Benutzer ohne eigenen Avatar.
|
||||
smtp:
|
||||
page_title: SMTP
|
||||
from_email:
|
||||
label: From Email
|
||||
msg: From email cannot be empty.
|
||||
text: The email address which emails are sent from.
|
||||
label: Absender-E-Mail
|
||||
msg: Von E-Mail darf nicht leer sein.
|
||||
text: Die E-Mail-Adresse, von der E-Mails gesendet werden.
|
||||
from_name:
|
||||
label: From Name
|
||||
msg: From name cannot be empty.
|
||||
text: The name which emails are sent from.
|
||||
label: Von Namen
|
||||
msg: Absendername darf nicht leer sein.
|
||||
text: Der Name, von dem E-Mails gesendet werden.
|
||||
smtp_host:
|
||||
label: SMTP Host
|
||||
msg: SMTP host cannot be empty.
|
||||
text: Your mail server.
|
||||
label: SMTP-Host
|
||||
msg: Der SMTP-Host darf nicht leer sein.
|
||||
text: Ihr Mailserver.
|
||||
encryption:
|
||||
label: Encryption
|
||||
msg: Encryption cannot be empty.
|
||||
text: For most servers SSL is the recommended option.
|
||||
label: Verschlüsselung
|
||||
msg: Verschlüsselung darf nicht leer sein.
|
||||
text: Für die meisten Server ist SSL die empfohlene Option.
|
||||
ssl: SSL
|
||||
none: None
|
||||
none: Keine
|
||||
smtp_port:
|
||||
label: SMTP-Port
|
||||
msg: SMTP port must be number 1 ~ 65535.
|
||||
text: The port to your mail server.
|
||||
msg: SMTP-Port muss Nummer 1 ~ 65535 sein.
|
||||
text: Der Port zu Ihrem Mailserver.
|
||||
smtp_username:
|
||||
label: SMTP Username
|
||||
msg: SMTP username cannot be empty.
|
||||
label: SMTP-Benutzername
|
||||
msg: Der SMTP-Benutzername darf nicht leer sein.
|
||||
smtp_password:
|
||||
label: SMTP Password
|
||||
msg: SMTP password cannot be empty.
|
||||
label: SMTP-Passwort
|
||||
msg: Das SMTP-Passwort darf nicht leer sein.
|
||||
test_email_recipient:
|
||||
label: Test Email Recipients
|
||||
text: Provide email address that will receive test sends.
|
||||
msg: Test email recipients is invalid
|
||||
label: E-Mail-Empfänger testen
|
||||
text: Geben Sie eine E-Mail-Adresse an, die Testsendungen erhalten soll.
|
||||
msg: Test-E-Mail-Empfänger ist ungültig
|
||||
smtp_authentication:
|
||||
label: Enable authentication
|
||||
title: SMTP Authentication
|
||||
msg: SMTP authentication cannot be empty.
|
||||
"yes": "Yes"
|
||||
"no": "No"
|
||||
label: Authentifizierung aktivieren
|
||||
title: SMTP-Authentifizierung
|
||||
msg: Die SMTP-Authentifizierung darf nicht leer sein.
|
||||
"yes": "Ja"
|
||||
"no": "Nein"
|
||||
branding:
|
||||
page_title: Branding
|
||||
logo:
|
||||
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.
|
||||
msg: Logo darf nicht leer sein.
|
||||
text: Das Logobild oben links auf Ihrer Website. Verwenden Sie ein breites rechteckiges Bild mit einer Höhe von 56 und einem Seitenverhältnis von mehr als 3:1. Wenn das Feld leer gelassen wird, wird der Seitentiteltext angezeigt.
|
||||
mobile_logo:
|
||||
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.
|
||||
label: Handy-Logo
|
||||
text: Das Logo, das auf der mobilen Version Ihrer Website verwendet wird. Verwenden Sie ein breites rechteckiges Bild mit einer Höhe von 56. Wenn Sie dieses Feld leer lassen, wird das Bild aus der Einstellung "Logo" verwendet.
|
||||
square_icon:
|
||||
label: Square Icon
|
||||
msg: Square icon cannot be empty.
|
||||
text: Image used as the base for metadata icons. Should ideally be larger than 512x512.
|
||||
label: Quadratisches Symbol
|
||||
msg: Quadratisches Symbol darf nicht leer sein.
|
||||
text: Bild, das als Basis für Metadatensymbole verwendet wird. Sollte idealerweise größer als 512x512 sein.
|
||||
favicon:
|
||||
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.
|
||||
text: Ein Favicon für Ihre Website. Um korrekt über ein CDN zu funktionieren, muss es ein PNG sein. Wird auf 32x32 verkleinert. Wenn das Feld leer gelassen wird, wird "quadratisches Symbol" verwendet.
|
||||
legal:
|
||||
page_title: Legal
|
||||
page_title: Gesetzlich
|
||||
terms_of_service:
|
||||
label: Terms of Service
|
||||
text: "You can add terms of service content here. If you already have a document hosted elsewhere, provide the full URL here."
|
||||
label: Nutzungsbedingungen
|
||||
text: "Hier können Sie Inhalt der Nutzungsbedingungen hinzufügen. Wenn Sie bereits ein Dokument haben, das woanders gehostet wird, geben Sie hier die vollständige URL an."
|
||||
privacy_policy:
|
||||
label: Privacy Policy
|
||||
text: "You can add privacy policy content here. If you already have a document hosted elsewhere, provide the full URL here."
|
||||
label: Datenschutz-Bestimmungen
|
||||
text: "Hier können Sie Inhalte zur Datenschutzrichtlinie hinzufügen. Wenn Sie bereits ein Dokument haben, das woanders gehostet wird, geben Sie hier die vollständige URL an."
|
||||
write:
|
||||
page_title: Write
|
||||
page_title: Schreiben
|
||||
recommend_tags:
|
||||
label: Recommend Tags
|
||||
text: "Please input tag slug above, one tag per line."
|
||||
label: Tags empfehlen
|
||||
text: "Bitte geben Sie den Tag-Slug oben ein, ein Tag pro Zeile."
|
||||
required_tag:
|
||||
title: Required Tag
|
||||
label: Set recommend tag as required
|
||||
text: "Every new question must have at least one recommend tag."
|
||||
title: Erforderliches Tag
|
||||
label: Legen Sie das Empfehlungs-Tag nach Bedarf fest
|
||||
text: "Jede neue Frage muss mindestens ein Empfehlungs-Tag haben."
|
||||
reserved_tags:
|
||||
label: Reserved Tags
|
||||
text: "Reserved tags can only be added to a post by moderator."
|
||||
label: Reservierte Tags
|
||||
text: "Reservierte Tags können nur vom Moderator zu einem Beitrag hinzugefügt werden."
|
||||
seo:
|
||||
page_title: SEO
|
||||
permalink:
|
||||
label: Permalink
|
||||
text: Custom URL structures can improve the usability, and forward-compatibility of your links.
|
||||
label: Dauerlink
|
||||
text: Benutzerdefinierte URL-Strukturen können die Benutzerfreundlichkeit und Aufwärtskompatibilität Ihrer Links verbessern.
|
||||
robots:
|
||||
label: robots.txt
|
||||
text: This will permanently override any related site settings.
|
||||
text: Dadurch werden alle zugehörigen Site-Einstellungen dauerhaft überschrieben.
|
||||
themes:
|
||||
page_title: Themes
|
||||
page_title: Themen
|
||||
themes:
|
||||
label: Themes
|
||||
text: Select an existing theme.
|
||||
label: Themen
|
||||
text: Wählen Sie ein vorhandenes Thema aus.
|
||||
navbar_style:
|
||||
label: Navbar Style
|
||||
text: Select an existing theme.
|
||||
label: Navbar-Stil
|
||||
text: Wählen Sie ein vorhandenes Thema aus.
|
||||
primary_color:
|
||||
label: Primary Color
|
||||
text: Modify the colors used by your themes
|
||||
label: Primärfarbe
|
||||
text: Ändern Sie die von Ihren Designs verwendeten Farben
|
||||
css_and_html:
|
||||
page_title: CSS and HTML
|
||||
custom_css:
|
||||
label: Custom CSS
|
||||
text: This will insert as <link>
|
||||
label: Benutzerdefinierte CSS
|
||||
text: Dies wird als <link> eingefügt
|
||||
head:
|
||||
label: Head
|
||||
text: This will insert before </head>
|
||||
label: Kopf
|
||||
text: Dies wird vor </head> eingefügt
|
||||
header:
|
||||
label: Header
|
||||
text: This will insert after <body>
|
||||
text: Dies wird nach <body> eingefügt
|
||||
footer:
|
||||
label: Footer
|
||||
text: This will insert before </html>.
|
||||
label: Fusszeile
|
||||
text: Dies wird vor </html> eingefügt.
|
||||
login:
|
||||
page_title: Login
|
||||
page_title: Anmeldung
|
||||
membership:
|
||||
title: Membership
|
||||
label: Allow new registrations
|
||||
text: Turn off to prevent anyone from creating a new account.
|
||||
title: Mitgliedschaft
|
||||
label: Neuregistrierungen zulassen
|
||||
text: Deaktivieren Sie diese Option, um zu verhindern, dass jemand ein neues Konto erstellt.
|
||||
private:
|
||||
title: Private
|
||||
label: Login required
|
||||
text: Only logged in users can access this community.
|
||||
title: Privatgelände
|
||||
label: Anmeldung erforderlich
|
||||
text: Nur angemeldete Benutzer können auf diese Community zugreifen.
|
||||
form:
|
||||
optional: (optional)
|
||||
empty: cannot be empty
|
||||
invalid: is invalid
|
||||
btn_submit: Save
|
||||
not_found_props: "Required property {{ key }} not found."
|
||||
empty: kann nicht leer sein
|
||||
invalid: ist ungültig
|
||||
btn_submit: Speichern
|
||||
not_found_props: "Erforderliche Eigenschaft {{ key }} nicht gefunden."
|
||||
page_review:
|
||||
review: Review
|
||||
proposed: proposed
|
||||
question_edit: Question edit
|
||||
answer_edit: Answer edit
|
||||
tag_edit: Tag edit
|
||||
edit_summary: Edit summary
|
||||
edit_question: Edit question
|
||||
edit_answer: Edit answer
|
||||
edit_tag: Edit tag
|
||||
empty: No review tasks left.
|
||||
review: Rezension
|
||||
proposed: vorgeschlagen
|
||||
question_edit: Frage bearbeiten
|
||||
answer_edit: Antwort bearbeiten
|
||||
tag_edit: Tag bearbeiten
|
||||
edit_summary: Zusammenfassung bearbeiten
|
||||
edit_question: Frage bearbeiten
|
||||
edit_answer: Antwort bearbeiten
|
||||
edit_tag: Tag bearbeiten
|
||||
empty: Keine Überprüfungsaufgaben mehr übrig.
|
||||
timeline:
|
||||
undeleted: undeleted
|
||||
deleted: deleted
|
||||
downvote: downvote
|
||||
upvote: upvote
|
||||
accept: accept
|
||||
cancelled: cancelled
|
||||
commented: commented
|
||||
rollback: rollback
|
||||
edited: edited
|
||||
answered: answered
|
||||
asked: asked
|
||||
closed: closed
|
||||
reopened: reopened
|
||||
created: created
|
||||
title: "History for"
|
||||
tag_title: "Timeline for"
|
||||
show_votes: "Show votes"
|
||||
undeleted: ungelöscht
|
||||
deleted: gelöscht
|
||||
downvote: ablehnen
|
||||
upvote: positiv bewerten
|
||||
accept: akzeptieren
|
||||
cancelled: abgebrochen
|
||||
commented: kommentiert
|
||||
rollback: zurückrollen
|
||||
edited: bearbeitet
|
||||
answered: antwortete
|
||||
asked: fragte
|
||||
closed: geschlossen
|
||||
reopened: wiedereröffnet
|
||||
created: erstellt
|
||||
pin: pinned
|
||||
unpin: unpinned
|
||||
show: listed
|
||||
hide: unlisted
|
||||
title: "Geschichte für"
|
||||
tag_title: "Zeitleiste für"
|
||||
show_votes: "Stimmen anzeigen"
|
||||
n_or_a: N/A
|
||||
title_for_question: "Timeline for"
|
||||
title_for_answer: "Timeline for answer to {{ title }} by {{ author }}"
|
||||
title_for_tag: "Timeline for tag"
|
||||
datetime: Datetime
|
||||
type: Type
|
||||
by: By
|
||||
comment: Comment
|
||||
no_data: "We couldn't find anything."
|
||||
title_for_question: "Zeitleiste für"
|
||||
title_for_answer: "Zeitachse für die Antwort auf {{ title }} von {{ author }}"
|
||||
title_for_tag: "Zeitachse für Tag"
|
||||
datetime: Terminzeit
|
||||
type: Typ
|
||||
by: Von
|
||||
comment: Kommentar
|
||||
no_data: "Wir konnten nichts finden."
|
||||
users:
|
||||
title: Users
|
||||
title: Benutzer
|
||||
users_with_the_most_reputation: Users with the highest reputation scores this week
|
||||
users_with_the_most_vote: Users who voted the most this week
|
||||
staffs: Our community staff
|
||||
reputation: reputation
|
||||
votes: votes
|
||||
staffs: Benutzer
|
||||
reputation: ruf
|
||||
votes: stimmen
|
||||
prompt:
|
||||
leave_page: Are you sure you want to leave the page?
|
||||
changes_not_save: Your changes may not be saved.
|
||||
leave_page: Möchten Sie die Seite wirklich verlassen?
|
||||
changes_not_save: Ihre Änderungen werden möglicherweise nicht gespeichert.
|
||||
draft:
|
||||
discard_confirm: Are you sure you want to discard your draft?
|
||||
discard_confirm: Möchten Sie Ihren Entwurf wirklich verwerfen?
|
||||
messages:
|
||||
post_deleted: This post has been deleted.
|
||||
|
||||
post_deleted: Dieser Beitrag wurde gelöscht.
|
||||
post_pin: This post has been pinned.
|
||||
post_unpin: This post has been unpinned.
|
||||
post_hide_list: This post has been hidden from list.
|
||||
post_show_list: This post has been shown to list.
|
||||
post_reopen: This post has been reopened.
|
||||
|
|
123
i18n/en_US.yaml
123
i18n/en_US.yaml
|
@ -25,6 +25,14 @@ backend:
|
|||
other: Reopen
|
||||
forbidden_error:
|
||||
other: Forbidden.
|
||||
pin:
|
||||
other: Pin
|
||||
hide:
|
||||
other: Unlist
|
||||
unpin:
|
||||
other: Unpin
|
||||
show:
|
||||
other: List
|
||||
role:
|
||||
name:
|
||||
user:
|
||||
|
@ -40,6 +48,58 @@ backend:
|
|||
other: Have the full power to access the site.
|
||||
moderator:
|
||||
other: Has access to all posts except admin settings.
|
||||
privilege:
|
||||
level_1:
|
||||
description:
|
||||
other: Level 1 (less reputation required for private team, group)
|
||||
level_2:
|
||||
description:
|
||||
other: Level 2 (low reputation required for startup community)
|
||||
level_3:
|
||||
description:
|
||||
other: Level 3 (high reputation required for mature community)
|
||||
rank_question_add_label:
|
||||
other: Ask question
|
||||
rank_answer_add_label:
|
||||
other: Write answer
|
||||
rank_comment_add_label:
|
||||
other: Write comment
|
||||
rank_report_add_label:
|
||||
other: Flag
|
||||
rank_comment_vote_up_label:
|
||||
other: Upvote comment
|
||||
rank_link_url_limit_label:
|
||||
other: Post more than 2 links at a time
|
||||
rank_question_vote_up_label:
|
||||
other: Upvote question
|
||||
rank_answer_vote_up_label:
|
||||
other: Upvote answer
|
||||
rank_question_vote_down_label:
|
||||
other: Downvote question
|
||||
rank_answer_vote_down_label:
|
||||
other: Downvote answer
|
||||
rank_tag_add_label:
|
||||
other: Create new tag
|
||||
rank_tag_edit_label:
|
||||
other: Edit tag description (need to review)
|
||||
rank_question_edit_label:
|
||||
other: Edit other's question (need to review)
|
||||
rank_answer_edit_label:
|
||||
other: Edit other's answer (need to review)
|
||||
rank_question_edit_without_review_label:
|
||||
other: Edit other's question without review
|
||||
rank_answer_edit_without_review_label:
|
||||
other: Edit other's answer without review
|
||||
rank_question_audit_label:
|
||||
other: Review question edits
|
||||
rank_answer_audit_label:
|
||||
other: Review answer edits
|
||||
rank_tag_audit_label:
|
||||
other: Review tag edits
|
||||
rank_tag_edit_without_review_label:
|
||||
other: Edit tag description without review
|
||||
rank_tag_synonym_label:
|
||||
other: Manage tag synonyms
|
||||
email:
|
||||
other: Email
|
||||
password:
|
||||
|
@ -78,7 +138,7 @@ backend:
|
|||
verify_url_expired:
|
||||
other: Email verified URL has expired, please resend the email.
|
||||
illegal_email_domain_error:
|
||||
other: The domain name of the current email address cannot be registered.
|
||||
other: Email is not allowed from that email domain. Please use another one.
|
||||
lang:
|
||||
not_found:
|
||||
other: Language file not found.
|
||||
|
@ -170,6 +230,10 @@ backend:
|
|||
other: You cannot modify your role.
|
||||
not_allowed_registration:
|
||||
other: Currently the site is not open for registration
|
||||
access_denied:
|
||||
other: Access denied
|
||||
page_access_denied:
|
||||
other: You do not have access to this page.
|
||||
config:
|
||||
read_config_failed:
|
||||
other: Read config failed
|
||||
|
@ -814,6 +878,7 @@ ui:
|
|||
btn: Add question
|
||||
answers: answers
|
||||
question_detail:
|
||||
action: Action
|
||||
Asked: Asked
|
||||
asked: asked
|
||||
update: Modified
|
||||
|
@ -852,13 +917,14 @@ ui:
|
|||
li1_2: Back up any statements you make with references or personal experience.
|
||||
header_2: But <strong>avoid</strong> ...
|
||||
li2_1: Asking for help, seeking clarification, or responding to other answers.
|
||||
|
||||
reopen:
|
||||
confirm_btn: Reopen
|
||||
title: Reopen this post
|
||||
content: Are you sure you want to reopen?
|
||||
success: This post has been reopened
|
||||
|
||||
pin:
|
||||
title: Pin this post
|
||||
content: Are you sure you wish to pinned globally? This post will appear at the top of all post lists.
|
||||
confirm_btn: Pin
|
||||
delete:
|
||||
title: Delete this post
|
||||
question: >-
|
||||
|
@ -872,7 +938,6 @@ ui:
|
|||
of accepted answers can result in your account being blocked from answering.
|
||||
Are you sure you wish to delete?
|
||||
other: Are you sure you wish to delete?
|
||||
tip_question_deleted: This post has been deleted
|
||||
tip_answer_deleted: This answer has been deleted
|
||||
btns:
|
||||
confirm: Confirm
|
||||
|
@ -888,6 +953,7 @@ ui:
|
|||
reject: Reject
|
||||
skip: Skip
|
||||
discard_draft: Discard draft
|
||||
pinned: Pinned
|
||||
search:
|
||||
title: Search Results
|
||||
keywords: Keywords
|
||||
|
@ -1110,14 +1176,16 @@ ui:
|
|||
seo: SEO
|
||||
customize: Customize
|
||||
themes: Themes
|
||||
css-html: CSS/HTML
|
||||
css_html: CSS/HTML
|
||||
login: Login
|
||||
privileges: Privileges
|
||||
plugins: Plugins
|
||||
installed_plugins: Installed Plugins
|
||||
website_welcome: Welcome to {{site_name}}
|
||||
plugins:
|
||||
login: Login
|
||||
qrcode_login_tip: Please use {{ agentName }} to scan the QR code and log in.
|
||||
login_failed_email_tip: Login failed, please allow this app to access your email information before try again.
|
||||
oauth:
|
||||
connect: Connect with {{ auth_name }}
|
||||
remove: Remove {{ auth_name }}
|
||||
|
@ -1314,9 +1382,6 @@ ui:
|
|||
label: Timezone
|
||||
msg: Timezone cannot be empty.
|
||||
text: Choose a city in the same timezone as you.
|
||||
avatar:
|
||||
label: Default Avatar
|
||||
text: For users without a custom avatar of their own.
|
||||
smtp:
|
||||
page_title: SMTP
|
||||
from_email:
|
||||
|
@ -1426,6 +1491,9 @@ ui:
|
|||
footer:
|
||||
label: Footer
|
||||
text: This will insert before </html>.
|
||||
sidebar:
|
||||
label: Sidebar
|
||||
text: This will insert in sidebar.
|
||||
login:
|
||||
page_title: Login
|
||||
membership:
|
||||
|
@ -1460,7 +1528,30 @@ ui:
|
|||
deactivate: Deactivate
|
||||
activate: Activate
|
||||
settings: Settings
|
||||
|
||||
settings_users:
|
||||
title: Users
|
||||
avatar:
|
||||
label: Default Avatar
|
||||
text: For users without a custom avatar of their own.
|
||||
profile_editable:
|
||||
title: Profile Editable
|
||||
allow_update_display_name:
|
||||
label: Allow users to change their display name
|
||||
allow_update_username:
|
||||
label: Allow users to change their username
|
||||
allow_update_avatar:
|
||||
label: Allow users to change their profile image
|
||||
allow_update_bio:
|
||||
label: Allow users to change their about me
|
||||
allow_update_website:
|
||||
label: Allow users to change their website
|
||||
allow_update_location:
|
||||
label: Allow users to change their location
|
||||
privilege:
|
||||
title: Privileges
|
||||
level:
|
||||
label: Reputation required level
|
||||
text: Choose the reputation required for the privileges
|
||||
|
||||
form:
|
||||
optional: (optional)
|
||||
|
@ -1496,6 +1587,10 @@ ui:
|
|||
closed: closed
|
||||
reopened: reopened
|
||||
created: created
|
||||
pin: pinned
|
||||
unpin: unpinned
|
||||
show: listed
|
||||
hide: unlisted
|
||||
title: "History for"
|
||||
tag_title: "Timeline for"
|
||||
show_votes: "Show votes"
|
||||
|
@ -1521,5 +1616,9 @@ ui:
|
|||
draft:
|
||||
discard_confirm: Are you sure you want to discard your draft?
|
||||
messages:
|
||||
post_deleted: This post has been deleted.
|
||||
|
||||
post_deleted: This post has been deleted.
|
||||
post_pin: This post has been pinned.
|
||||
post_unpin: This post has been unpinned.
|
||||
post_hide_list: This post has been hidden from list.
|
||||
post_show_list: This post has been shown to list.
|
||||
post_reopen: This post has been reopened.
|
||||
|
|
692
i18n/es_ES.yaml
692
i18n/es_ES.yaml
|
@ -22,6 +22,14 @@ backend:
|
|||
other: Close
|
||||
reopen:
|
||||
other: Reopen
|
||||
pin:
|
||||
other: Pin
|
||||
hide:
|
||||
other: Unlist
|
||||
unpin:
|
||||
other: Unpin
|
||||
show:
|
||||
other: List
|
||||
role:
|
||||
name:
|
||||
user:
|
||||
|
@ -46,9 +54,9 @@ backend:
|
|||
error:
|
||||
admin:
|
||||
cannot_update_their_password:
|
||||
other: You cannot modify your password.
|
||||
other: No puede modificar su contraseña.
|
||||
cannot_modify_self_status:
|
||||
other: You cannot modify your status.
|
||||
other: No puede modificar su contraseña.
|
||||
email_or_password_wrong:
|
||||
other: Contraseña o correo incorrecto.
|
||||
answer:
|
||||
|
@ -131,7 +139,7 @@ backend:
|
|||
cannot_update:
|
||||
other: Sin permiso para actualizar.
|
||||
is_used_cannot_delete:
|
||||
other: You cannot delete a tag that is in use
|
||||
other: No puede eliminar una etiqueta que está en uso
|
||||
cannot_set_synonym_as_itself:
|
||||
other: No se puede establecer como sinónimo de una etiqueta la propia etiqueta.
|
||||
smtp:
|
||||
|
@ -176,7 +184,7 @@ backend:
|
|||
other: No es posible crear el archivo config.yaml.
|
||||
upload:
|
||||
unsupported_file_format:
|
||||
other: Unsupported file format.
|
||||
other: Formato de archivo no soportado.
|
||||
report:
|
||||
spam:
|
||||
name:
|
||||
|
@ -359,7 +367,7 @@ ui:
|
|||
msg:
|
||||
empty: Código no puede estar vacío.
|
||||
language:
|
||||
label: Language
|
||||
label: Idioma
|
||||
placeholder: Detección automática
|
||||
btn_cancel: Cancelar
|
||||
btn_confirm: Añadir
|
||||
|
@ -395,7 +403,7 @@ ui:
|
|||
only_image: Solo se permiten archivos de imagen.
|
||||
max_size: El tamaño del archivo no puede superar 4MB.
|
||||
desc:
|
||||
label: Description
|
||||
label: Descripción
|
||||
tab_url: URL de la imagen
|
||||
form_url:
|
||||
fields:
|
||||
|
@ -424,7 +432,7 @@ ui:
|
|||
msg:
|
||||
empty: La dirección no puede estar vacía.
|
||||
name:
|
||||
label: Description
|
||||
label: Descripción
|
||||
btn_cancel: Cancelar
|
||||
btn_confirm: Añadir
|
||||
ordered_list:
|
||||
|
@ -466,13 +474,13 @@ ui:
|
|||
range: Nombre a mostrar con un máximo de 35 caracteres.
|
||||
slug_name:
|
||||
label: URL amigable
|
||||
desc: 'Debe usar el conjunto de caracteres "a-z", "0-9", "+ # - ."'
|
||||
desc: Slug de URL de hasta 35 caracteres.
|
||||
msg:
|
||||
empty: URL no puede estar vacío.
|
||||
range: URL slug hasta 35 caracteres.
|
||||
character: La URL amigable contiene caracteres no permitidos.
|
||||
desc:
|
||||
label: Description
|
||||
label: Descripción
|
||||
btn_cancel: Cancelar
|
||||
btn_submit: Enviar
|
||||
btn_post: Post new tag
|
||||
|
@ -491,10 +499,10 @@ ui:
|
|||
delete:
|
||||
title: Eliminar esta etiqueta
|
||||
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>
|
||||
<p>No permitimos <strong>eliminar etiquetas con publicaciones</strong>.</p> <p>Primero elimine esta etiqueta de las publicaciones.</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?
|
||||
<p>No permitimos <strong>eliminar etiquetas con sinónimos</strong>.</p> <p>Elimine primero los sinónimos de esta etiqueta.</p>
|
||||
tip: '¿Estás seguro de que deseas borrarlo?'
|
||||
close: Cerrar
|
||||
edit_tag:
|
||||
title: Editar etiqueta
|
||||
|
@ -714,12 +722,12 @@ ui:
|
|||
display_name:
|
||||
label: Nombre a mostrar
|
||||
msg: El nombre a mostrar no puede estar vacío.
|
||||
msg_range: Display name up to 30 characters.
|
||||
msg_range: Mostrar nombre de hasta 30 caracteres.
|
||||
username:
|
||||
label: Nombre de usuario
|
||||
caption: La gente puede mencionarte con "@nombredeusuario".
|
||||
msg: El nombre de usuario no puede estar vacío.
|
||||
msg_range: Username up to 30 characters.
|
||||
msg_range: Nombre de usuario de hasta 30 caracteres.
|
||||
character: 'Debe usar el conjunto de caracteres "a-z", "0-9", " - . _"'
|
||||
avatar:
|
||||
label: Imagen de perfil
|
||||
|
@ -731,13 +739,13 @@ ui:
|
|||
default: Sistema
|
||||
msg: Por favor, sube una imagen
|
||||
bio:
|
||||
label: About Me
|
||||
label: Sobre mí
|
||||
website:
|
||||
label: Website
|
||||
label: Sitio Web
|
||||
placeholder: "https://example.com"
|
||||
msg: Formato del sitio web incorrecto
|
||||
location:
|
||||
label: Location
|
||||
label: Ubicación
|
||||
placeholder: "Ciudad, País"
|
||||
notification:
|
||||
heading: Notificaciones
|
||||
|
@ -753,7 +761,7 @@ ui:
|
|||
email:
|
||||
label: Correo electrónico
|
||||
msg: El correo electrónico no puede estar vacío.
|
||||
password_title: Password
|
||||
password_title: Contraseña
|
||||
current_pass:
|
||||
label: Contraseña actual
|
||||
msg:
|
||||
|
@ -780,6 +788,7 @@ ui:
|
|||
btn: Añadir pregunta
|
||||
answers: respuestas
|
||||
question_detail:
|
||||
action: Action
|
||||
Asked: Preguntada
|
||||
asked: preguntada
|
||||
update: Modificada
|
||||
|
@ -810,7 +819,7 @@ ui:
|
|||
confirm_info: >-
|
||||
<p>¿Seguro que quieres añadir otra respuesta?</p><p>Puedes utilizar el enlace de edición para detallar y mejorar tu respuesta existente en su lugar.</p>
|
||||
empty: La respuesta no puede estar vacía.
|
||||
characters: content must be at least 6 characters in length.
|
||||
characters: el contenido debe tener al menos 6 caracteres.
|
||||
tips:
|
||||
header_1: Thanks for your answer
|
||||
li1_1: Please be sure to <strong>answer the question</strong>. Provide details and share your research.
|
||||
|
@ -821,7 +830,10 @@ ui:
|
|||
confirm_btn: Reopen
|
||||
title: Reabrir esta publicación
|
||||
content: '¿Seguro que quieres reabrir esta publicación?'
|
||||
success: Esta publicación ha sido reabierta
|
||||
pin:
|
||||
title: Pin this post
|
||||
content: Are you sure you wish to pinned globally? This post will appear at the top of all post lists.
|
||||
confirm_btn: Pin
|
||||
delete:
|
||||
title: Eliminar esta publicación
|
||||
question: >-
|
||||
|
@ -833,7 +845,6 @@ ui:
|
|||
|
||||
El borrado repetido de respuestas aceptadas puede resultar en que tu cuenta se bloquee para responder. ¿Estás seguro de que deseas borrarlo?
|
||||
other: '¿Estás seguro de que deseas borrarlo?'
|
||||
tip_question_deleted: Esta publicación ha sido eliminada
|
||||
tip_answer_deleted: Esta respuesta ha sido eliminada
|
||||
btns:
|
||||
confirm: Confirmar
|
||||
|
@ -848,7 +859,8 @@ ui:
|
|||
approve: Aprobar
|
||||
reject: Rechazar
|
||||
skip: Omitir
|
||||
discard_draft: Discard draft
|
||||
discard_draft: Descartar borrador
|
||||
pinned: Pinned
|
||||
search:
|
||||
title: Resultados de la búsqueda
|
||||
keywords: Palabras claves
|
||||
|
@ -883,7 +895,7 @@ ui:
|
|||
modal_confirm:
|
||||
title: Error...
|
||||
account_result:
|
||||
page_title: Welcome to {{site_name}}
|
||||
page_title: Bienvenido a {{site_name}}
|
||||
success: Tu nueva cuenta ha sido confirmada, serás redirigido a la página de inicio.
|
||||
link: Continuar a la página de inicio
|
||||
invalid: >-
|
||||
|
@ -894,7 +906,7 @@ ui:
|
|||
unsubscribe:
|
||||
page_title: Desuscribir
|
||||
success_title: Desuscrito con éxito
|
||||
success_desc: You have been successfully removed from this subscriber list and won't receive any further emails from us.
|
||||
success_desc: Ha sido eliminado con éxito de esta lista de suscriptores y no recibirá más correos electrónicos nuestros.
|
||||
link: Cambiar ajustes
|
||||
question:
|
||||
following_tags: Etiquetas seguidas
|
||||
|
@ -934,109 +946,109 @@ ui:
|
|||
visited_x_days: "Visitado {{ count }} días"
|
||||
viewed: Visto
|
||||
joined: Unido
|
||||
last_login: Seen
|
||||
last_login: Visto
|
||||
about_me: Sobre mí
|
||||
about_me_empty: "// ¡Hola Mundo!"
|
||||
top_answers: Mejores respuestas
|
||||
top_questions: Top Questions
|
||||
top_questions: Preguntas Principales
|
||||
stats: Estadísticas
|
||||
list_empty: No posts found.<br />Perhaps you'd like to select a different tab?
|
||||
list_empty: No se encontraron publicaciones.<br />¿Quizás le gustaría seleccionar una pestaña diferente?
|
||||
accepted: Aceptada
|
||||
answered: answered
|
||||
asked: asked
|
||||
upvote: upvote
|
||||
downvote: downvote
|
||||
mod_short: Mod
|
||||
mod_long: Moderators
|
||||
x_reputation: reputation
|
||||
x_votes: votes received
|
||||
x_answers: answers
|
||||
x_questions: questions
|
||||
answered: respondida
|
||||
asked: preguntó
|
||||
upvote: votar a favor
|
||||
downvote: voto negativo
|
||||
mod_short: Modificación
|
||||
mod_long: Moderadores
|
||||
x_reputation: reputación
|
||||
x_votes: votos recibidos
|
||||
x_answers: respuestas
|
||||
x_questions: preguntas
|
||||
install:
|
||||
title: Answer
|
||||
next: Next
|
||||
done: Done
|
||||
config_yaml_error: Can't create the config.yaml file.
|
||||
title: Respuesta
|
||||
next: Próximo
|
||||
done: Hecho
|
||||
config_yaml_error: No se puede crear el archivo config.yaml.
|
||||
lang:
|
||||
label: Please Choose a Language
|
||||
label: Elija un idioma
|
||||
db_type:
|
||||
label: Database Engine
|
||||
label: Motor de base de datos
|
||||
db_username:
|
||||
label: Username
|
||||
placeholder: root
|
||||
msg: Username cannot be empty.
|
||||
label: Nombre de usuario
|
||||
placeholder: raíz
|
||||
msg: El nombre de usuario no puede estar vacío.
|
||||
db_password:
|
||||
label: Password
|
||||
placeholder: root
|
||||
msg: Password cannot be empty.
|
||||
label: Contraseña
|
||||
placeholder: raíz
|
||||
msg: La contraseña no puede estar vacía.
|
||||
db_host:
|
||||
label: Database Host
|
||||
label: Anfitrión de la base de datos
|
||||
placeholder: "db:3306"
|
||||
msg: Database Host cannot be empty.
|
||||
msg: El host de la base de datos no puede estar vacío.
|
||||
db_name:
|
||||
label: Database Name
|
||||
placeholder: answer
|
||||
msg: Database Name cannot be empty.
|
||||
label: Nombre de la base de datos
|
||||
placeholder: respuesta
|
||||
msg: El nombre de la base de datos no puede estar vacío.
|
||||
db_file:
|
||||
label: Database File
|
||||
placeholder: /data/answer.db
|
||||
msg: Database File cannot be empty.
|
||||
label: Archivo de base de datos
|
||||
placeholder: /data/respuesta.db
|
||||
msg: El archivo de la base de datos no puede estar vacío.
|
||||
config_yaml:
|
||||
title: Create config.yaml
|
||||
label: The config.yaml file created.
|
||||
title: Crear config.yaml
|
||||
label: El archivo config.yaml creado.
|
||||
desc: >-
|
||||
You can create the <1>config.yaml</1> file manually in the <1>/var/wwww/xxx/</1> directory and paste the following text into it.
|
||||
info: After you've done that, click "Next" button.
|
||||
site_information: Site Information
|
||||
admin_account: Admin Account
|
||||
Puede crear el archivo <1>config.yaml</1> manualmente en el directorio <1>/var/www/xxx/</1> y pegar el siguiente texto en él.
|
||||
info: Después de haber hecho eso, haga clic en el botón "Siguiente".
|
||||
site_information: Información del sitio
|
||||
admin_account: Cuenta de administrador
|
||||
site_name:
|
||||
label: Site Name
|
||||
msg: Site Name cannot be empty.
|
||||
label: Nombre del sitio
|
||||
msg: El nombre del sitio no puede estar vacío.
|
||||
site_url:
|
||||
label: Site URL
|
||||
text: The address of your site.
|
||||
label: Sitio URL
|
||||
text: La dirección de su sitio.
|
||||
msg:
|
||||
empty: Site URL cannot be empty.
|
||||
incorrect: Site URL incorrect format.
|
||||
empty: La URL del sitio no puede estar vacía.
|
||||
incorrect: Formato incorrecto de la URL del sitio.
|
||||
contact_email:
|
||||
label: Contact Email
|
||||
text: Email address of key contact responsible for this site.
|
||||
label: Email de contacto
|
||||
text: Dirección de correo electrónico del contacto clave responsable de este sitio.
|
||||
msg:
|
||||
empty: Contact Email cannot be empty.
|
||||
incorrect: Contact Email incorrect format.
|
||||
empty: El correo electrónico de contacto no puede estar vacío.
|
||||
incorrect: Correo electrónico de contacto formato incorrecto.
|
||||
admin_name:
|
||||
label: Name
|
||||
msg: Name cannot be empty.
|
||||
label: Nombre
|
||||
msg: El nombre no puede estar vacío.
|
||||
admin_password:
|
||||
label: Password
|
||||
label: Contraseña
|
||||
text: >-
|
||||
You will need this password to log in. Please store it in a secure location.
|
||||
msg: Password cannot be empty.
|
||||
Necesitará esta contraseña para iniciar sesión. Guárdela en un lugar seguro.
|
||||
msg: La contraseña no puede estar vacía.
|
||||
admin_email:
|
||||
label: Email
|
||||
text: You will need this email to log in.
|
||||
label: Correo electrónico
|
||||
text: Necesitará este correo electrónico para iniciar sesión.
|
||||
msg:
|
||||
empty: Email cannot be empty.
|
||||
incorrect: Email incorrect format.
|
||||
ready_title: Your Answer is Ready!
|
||||
empty: El correo electrónico no puede estar vacío.
|
||||
incorrect: Correo electrónico con formato incorrecto.
|
||||
ready_title: '¡Tu respuesta está lista!'
|
||||
ready_desc: >-
|
||||
If you ever feel like changing more settings, visit <1>admin section</1>; find it in the site menu.
|
||||
good_luck: "Have fun, and good luck!"
|
||||
warn_title: Warning
|
||||
Si alguna vez desea cambiar más configuraciones, visite la <1>sección de administración</1>; encuéntrelo en el menú del sitio.
|
||||
good_luck: "¡Diviértete y buena suerte!"
|
||||
warn_title: Advertencia
|
||||
warn_desc: >-
|
||||
The file <1>config.yaml</1> already exists. If you need to reset any of the configuration items in this file, please delete it first.
|
||||
install_now: You may try <1>installing now</1>.
|
||||
installed: Already installed
|
||||
El archivo <1>config.yaml</1> ya existe. Si necesita restablecer alguno de los elementos de configuración de este archivo, elimínelo primero.
|
||||
install_now: Puede intentar <1>instalar ahora</1>.
|
||||
installed: Ya instalado
|
||||
installed_desc: >-
|
||||
You appear to have already installed. To reinstall please clear your old database tables first.
|
||||
db_failed: Database connection failed
|
||||
Parece que ya lo has instalado. Para reinstalar, borre primero las tablas de la base de datos anterior.
|
||||
db_failed: La conexión a la base de datos falló
|
||||
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 host’s database server is down.
|
||||
Esto significa que la información de la base de datos en su archivo <1>config.yaml</1> es incorrecta o que no se pudo establecer contacto con el servidor de la base de datos. Esto podría significar que el servidor de la base de datos de su host está inactivo.
|
||||
counts:
|
||||
views: views
|
||||
votes: votes
|
||||
answers: answers
|
||||
accepted: Accepted
|
||||
views: puntos de vista
|
||||
votes: votos
|
||||
answers: respuestas
|
||||
accepted: Aceptado
|
||||
page_error:
|
||||
http_error: HTTP Error {{ code }}
|
||||
desc_403: You don’t have permission to access this page.
|
||||
|
@ -1044,323 +1056,323 @@ ui:
|
|||
desc_50X: The server encountered an error and could not complete your request.
|
||||
back_home: Back to homepage
|
||||
page_maintenance:
|
||||
desc: "We are under maintenance, we'll be back soon."
|
||||
desc: "Estamos en mantenimiento, pronto estaremos de vuelta."
|
||||
nav_menus:
|
||||
dashboard: Dashboard
|
||||
contents: Contents
|
||||
questions: Questions
|
||||
answers: Answers
|
||||
users: Users
|
||||
flags: Flags
|
||||
settings: Settings
|
||||
dashboard: Panel
|
||||
contents: Contenido
|
||||
questions: Preguntas
|
||||
answers: Respuestas
|
||||
users: Usuarios
|
||||
flags: Banderas
|
||||
settings: Ajustes
|
||||
general: General
|
||||
interface: Interface
|
||||
interface: Interfaz
|
||||
smtp: SMTP
|
||||
branding: Branding
|
||||
branding: Marca
|
||||
legal: Legal
|
||||
write: Write
|
||||
tos: Terms of Service
|
||||
privacy: Privacy
|
||||
seo: SEO
|
||||
customize: Customize
|
||||
themes: Themes
|
||||
write: Escribir
|
||||
tos: Términos de servicio
|
||||
privacy: Privacidad
|
||||
seo: ESTE
|
||||
customize: Personalizar
|
||||
themes: Temas
|
||||
css-html: CSS/HTML
|
||||
login: Login
|
||||
login: Iniciar sesión
|
||||
admin:
|
||||
admin_header:
|
||||
title: Admin
|
||||
title: Administrador
|
||||
dashboard:
|
||||
title: Dashboard
|
||||
welcome: Welcome to Answer Admin!
|
||||
site_statistics: Site Statistics
|
||||
questions: "Questions:"
|
||||
answers: "Answers:"
|
||||
comments: "Comments:"
|
||||
votes: "Votes:"
|
||||
active_users: "Active users:"
|
||||
flags: "Flags:"
|
||||
site_health_status: Site Health Status
|
||||
version: "Version:"
|
||||
title: Panel
|
||||
welcome: '¡Bienvenido a Answer Admin!'
|
||||
site_statistics: Estadísticas del sitio
|
||||
questions: "Preguntas:"
|
||||
answers: "Respuestas:"
|
||||
comments: "Comentarios:"
|
||||
votes: "Votos:"
|
||||
active_users: "Usuarios activos:"
|
||||
flags: "Banderas:"
|
||||
site_health_status: Estado de salud del sitio
|
||||
version: "Versión:"
|
||||
https: "HTTPS:"
|
||||
uploading_files: "Uploading files:"
|
||||
uploading_files: "Subiendo archivos:"
|
||||
smtp: "SMTP:"
|
||||
timezone: "Timezone:"
|
||||
system_info: System Info
|
||||
storage_used: "Storage used:"
|
||||
uptime: "Uptime:"
|
||||
answer_links: Answer Links
|
||||
documents: Documents
|
||||
feedback: Feedback
|
||||
support: Support
|
||||
review: Review
|
||||
config: Config
|
||||
update_to: Update to
|
||||
latest: Latest
|
||||
check_failed: Check failed
|
||||
"yes": "Yes"
|
||||
timezone: "Zona horaria:"
|
||||
system_info: Información del sistema
|
||||
storage_used: "Almacenamiento utilizado:"
|
||||
uptime: "Tiempo ejecutándose:"
|
||||
answer_links: Enlaces de respuesta
|
||||
documents: Documentos
|
||||
feedback: Comentario
|
||||
support: Soporte
|
||||
review: Revisar
|
||||
config: Configuración
|
||||
update_to: Actualizar para
|
||||
latest: Lo más nuevo
|
||||
check_failed: Comprobación fallida
|
||||
"yes": "Si"
|
||||
"no": "No"
|
||||
not_allowed: Not allowed
|
||||
allowed: Allowed
|
||||
enabled: Enabled
|
||||
disabled: Disabled
|
||||
not_allowed: No permitido
|
||||
allowed: Permitido
|
||||
enabled: Activado
|
||||
disabled: Desactivado
|
||||
flags:
|
||||
title: Flags
|
||||
pending: Pending
|
||||
completed: Completed
|
||||
flagged: Flagged
|
||||
created: Created
|
||||
action: Action
|
||||
review: Review
|
||||
title: Banderas
|
||||
pending: Pendiente
|
||||
completed: Terminado
|
||||
flagged: Marcado
|
||||
created: Creado
|
||||
action: Acción
|
||||
review: Revisar
|
||||
change_modal:
|
||||
title: Change user status to...
|
||||
btn_cancel: Cancel
|
||||
btn_submit: Submit
|
||||
title: Cambiar estado de usuario a...
|
||||
btn_cancel: Cancelar
|
||||
btn_submit: Entregar
|
||||
normal_name: normal
|
||||
normal_desc: A normal user can ask and answer questions.
|
||||
suspended_name: suspended
|
||||
suspended_desc: A suspended user can't log in.
|
||||
deleted_name: deleted
|
||||
deleted_desc: "Delete profile, authentication associations."
|
||||
inactive_name: inactive
|
||||
inactive_desc: An inactive user must re-validate their email.
|
||||
confirm_title: Delete this user
|
||||
confirm_content: Are you sure you want to delete this user? This is permanent!
|
||||
confirm_btn: Delete
|
||||
normal_desc: Un usuario normal puede hacer y responder preguntas.
|
||||
suspended_name: suspendido
|
||||
suspended_desc: Un usuario suspendido no puede iniciar sesión.
|
||||
deleted_name: eliminado
|
||||
deleted_desc: "Eliminar perfil, asociaciones de autenticación."
|
||||
inactive_name: inactivo
|
||||
inactive_desc: Un usuario inactivo debe volver a validar su correo electrónico.
|
||||
confirm_title: Eliminar este usuario
|
||||
confirm_content: '¿Está seguro de que desea eliminar este usuario? ¡Esto es permanente!'
|
||||
confirm_btn: Borrar
|
||||
msg:
|
||||
empty: Please select a reason.
|
||||
empty: Por favor seleccione una razón.
|
||||
status_modal:
|
||||
title: "Change {{ type }} status to..."
|
||||
title: "Cambiar el estado de {{ type }} a..."
|
||||
normal_name: normal
|
||||
normal_desc: A normal post available to everyone.
|
||||
closed_name: closed
|
||||
closed_desc: "A closed question can't answer, but still can edit, vote and comment."
|
||||
deleted_name: deleted
|
||||
deleted_desc: All reputation gained and lost will be restored.
|
||||
btn_cancel: Cancel
|
||||
btn_submit: Submit
|
||||
btn_next: Next
|
||||
normal_desc: Un puesto normal al alcance de todos.
|
||||
closed_name: cerrada
|
||||
closed_desc: "Una pregunta cerrada no puede responder, pero aún puede editar, votar y comentar."
|
||||
deleted_name: eliminado
|
||||
deleted_desc: Toda la reputación ganada y perdida será restaurada.
|
||||
btn_cancel: Cancelar
|
||||
btn_submit: Entregar
|
||||
btn_next: Próximo
|
||||
user_role_modal:
|
||||
title: Change user role to...
|
||||
btn_cancel: Cancel
|
||||
btn_submit: Submit
|
||||
title: Cambiar rol de usuario a...
|
||||
btn_cancel: Cancelar
|
||||
btn_submit: Entregar
|
||||
users:
|
||||
title: Users
|
||||
name: Name
|
||||
email: Email
|
||||
reputation: Reputation
|
||||
created_at: Created Time
|
||||
delete_at: Deleted Time
|
||||
suspend_at: Suspended Time
|
||||
status: Status
|
||||
title: Usuarios
|
||||
name: Nombre
|
||||
email: Correo electrónico
|
||||
reputation: Reputación
|
||||
created_at: Creado Tiempo
|
||||
delete_at: Hora eliminada
|
||||
suspend_at: Tiempo suspendido
|
||||
status: Estado
|
||||
role: Role
|
||||
action: Action
|
||||
change: Change
|
||||
all: All
|
||||
staff: Staff
|
||||
inactive: Inactive
|
||||
suspended: Suspended
|
||||
deleted: Deleted
|
||||
action: Acción
|
||||
change: Cambiar
|
||||
all: Todo
|
||||
staff: Personal
|
||||
inactive: Inactivo
|
||||
suspended: Suspendido
|
||||
deleted: Eliminado
|
||||
normal: Normal
|
||||
Moderator: Moderator
|
||||
Admin: Admin
|
||||
User: User
|
||||
Moderator: Moderador
|
||||
Admin: Administrador
|
||||
User: Usuario
|
||||
filter:
|
||||
placeholder: "Filter by name, user:id"
|
||||
set_new_password: Set new password
|
||||
change_status: Change status
|
||||
change_role: Change role
|
||||
show_logs: Show logs
|
||||
add_user: Add user
|
||||
placeholder: "Filtrar por nombre, usuario:id"
|
||||
set_new_password: Establecer nueva contraseña
|
||||
change_status: Cambiar Estado
|
||||
change_role: Cambiar rol
|
||||
show_logs: Mostrar registros
|
||||
add_user: Agregar usuario
|
||||
new_password_modal:
|
||||
title: Set new password
|
||||
title: Establecer nueva contraseña
|
||||
form:
|
||||
fields:
|
||||
password:
|
||||
label: Password
|
||||
text: The user will be logged out and need to login again.
|
||||
msg: Password must be at 8-32 characters in length.
|
||||
btn_cancel: Cancel
|
||||
btn_submit: Submit
|
||||
label: Contraseña
|
||||
text: El usuario se desconectará y deberá volver a iniciar sesión.
|
||||
msg: La contraseña debe tener entre 8 y 32 caracteres de longitud.
|
||||
btn_cancel: Cancelar
|
||||
btn_submit: Entregar
|
||||
user_modal:
|
||||
title: Add new user
|
||||
title: Añadir nuevo usuario
|
||||
form:
|
||||
fields:
|
||||
display_name:
|
||||
label: Display Name
|
||||
msg: Display Name must be at 3-30 characters in length.
|
||||
label: Nombre para mostrar
|
||||
msg: El nombre para mostrar debe tener entre 3 y 30 caracteres de longitud.
|
||||
email:
|
||||
label: Email
|
||||
msg: Email is not valid.
|
||||
label: Correo electrónico
|
||||
msg: El correo no es válido.
|
||||
password:
|
||||
label: Password
|
||||
msg: Password must be at 8-32 characters in length.
|
||||
btn_cancel: Cancel
|
||||
btn_submit: Submit
|
||||
label: Contraseña
|
||||
msg: La contraseña debe tener entre 8 y 32 caracteres de longitud.
|
||||
btn_cancel: Cancelar
|
||||
btn_submit: Entregar
|
||||
questions:
|
||||
page_title: Questions
|
||||
page_title: Preguntas
|
||||
normal: Normal
|
||||
closed: Closed
|
||||
deleted: Deleted
|
||||
post: Post
|
||||
votes: Votes
|
||||
answers: Answers
|
||||
created: Created
|
||||
status: Status
|
||||
action: Action
|
||||
change: Change
|
||||
closed: Cerrado
|
||||
deleted: Eliminado
|
||||
post: Correo
|
||||
votes: Votos
|
||||
answers: Respuestas
|
||||
created: Creado
|
||||
status: Estado
|
||||
action: Acción
|
||||
change: Cambiar
|
||||
filter:
|
||||
placeholder: "Filter by title, question:id"
|
||||
placeholder: "Filtrar por título, pregunta:id"
|
||||
answers:
|
||||
page_title: Answers
|
||||
page_title: Respuestas
|
||||
normal: Normal
|
||||
deleted: Deleted
|
||||
post: Post
|
||||
votes: Votes
|
||||
created: Created
|
||||
status: Status
|
||||
action: Action
|
||||
change: Change
|
||||
deleted: Eliminado
|
||||
post: Correo
|
||||
votes: Votos
|
||||
created: Creado
|
||||
status: Estado
|
||||
action: Acción
|
||||
change: Cambiar
|
||||
filter:
|
||||
placeholder: "Filter by title, answer:id"
|
||||
placeholder: "Filtrar por título, respuesta: id"
|
||||
general:
|
||||
page_title: General
|
||||
name:
|
||||
label: Site Name
|
||||
msg: Site name cannot be empty.
|
||||
text: "The name of this site, as used in the title tag."
|
||||
label: Nombre del sitio
|
||||
msg: El nombre del sitio no puede estar vacío.
|
||||
text: "El nombre de este sitio, tal como se usa en la etiqueta del título."
|
||||
site_url:
|
||||
label: Site URL
|
||||
msg: Site url cannot be empty.
|
||||
validate: Please enter a valid URL.
|
||||
text: The address of your site.
|
||||
label: Sitio URL
|
||||
msg: La url del sitio no puede estar vacía.
|
||||
validate: Por favor introduzca un URL válido.
|
||||
text: La dirección de su sitio.
|
||||
short_desc:
|
||||
label: Short Site Description
|
||||
msg: Short site description cannot be empty.
|
||||
text: "Short description, as used in the title tag on homepage."
|
||||
label: Breve descripción del sitio
|
||||
msg: La descripción breve del sitio no puede estar vacía.
|
||||
text: "Breve descripción, tal como se usa en la etiqueta del título en la página de inicio."
|
||||
desc:
|
||||
label: Site Description
|
||||
msg: Site description cannot be empty.
|
||||
text: "Describe this site in one sentence, as used in the meta description tag."
|
||||
label: Descripción del lugar
|
||||
msg: La descripción del sitio no puede estar vacía.
|
||||
text: "Describa este sitio en una oración, como se usa en la etiqueta de meta descripción."
|
||||
contact_email:
|
||||
label: Contact Email
|
||||
msg: Contact email cannot be empty.
|
||||
validate: Contact email is not valid.
|
||||
text: Email address of key contact responsible for this site.
|
||||
label: Email de contacto
|
||||
msg: El correo electrónico de contacto no puede estar vacío.
|
||||
validate: El correo electrónico de contacto no es válido.
|
||||
text: Dirección de correo electrónico del contacto clave responsable de este sitio.
|
||||
interface:
|
||||
page_title: Interface
|
||||
page_title: Interfaz
|
||||
language:
|
||||
label: Interface Language
|
||||
msg: Interface language cannot be empty.
|
||||
text: User interface language. It will change when you refresh the page.
|
||||
label: Lenguaje de interfaz
|
||||
msg: El idioma de la interfaz no puede estar vacío.
|
||||
text: Idioma de la interfaz de usuario. Cambiará cuando actualice la página.
|
||||
time_zone:
|
||||
label: Timezone
|
||||
label: Zona horaria
|
||||
msg: Timezone cannot be empty.
|
||||
text: Choose a city in the same timezone as you.
|
||||
text: Elija una ciudad en la misma zona horaria que usted.
|
||||
avatar:
|
||||
label: Default Avatar
|
||||
text: For users without a custom avatar of their own.
|
||||
smtp:
|
||||
page_title: SMTP
|
||||
from_email:
|
||||
label: From Email
|
||||
msg: From email cannot be empty.
|
||||
text: The email address which emails are sent from.
|
||||
label: Desde el e-mail
|
||||
msg: Desde el correo electrónico no puede estar vacío.
|
||||
text: La dirección de correo electrónico desde la que se envían los correos electrónicos.
|
||||
from_name:
|
||||
label: From Name
|
||||
msg: From name cannot be empty.
|
||||
text: The name which emails are sent from.
|
||||
label: De nombre
|
||||
msg: Desde el nombre no puede estar vacío.
|
||||
text: El nombre desde el que se envían los correos electrónicos.
|
||||
smtp_host:
|
||||
label: SMTP Host
|
||||
msg: SMTP host cannot be empty.
|
||||
text: Your mail server.
|
||||
label: Anfitrión SMTP
|
||||
msg: El host SMTP no puede estar vacío.
|
||||
text: Su servidor de correo.
|
||||
encryption:
|
||||
label: Encryption
|
||||
msg: Encryption cannot be empty.
|
||||
text: For most servers SSL is the recommended option.
|
||||
label: Cifrado
|
||||
msg: El cifrado no puede estar vacío.
|
||||
text: Para la mayoría de los servidores, SSL es la opción recomendada.
|
||||
ssl: SSL
|
||||
none: None
|
||||
none: Ninguno
|
||||
smtp_port:
|
||||
label: SMTP Port
|
||||
msg: SMTP port must be number 1 ~ 65535.
|
||||
text: The port to your mail server.
|
||||
label: Puerto SMTP
|
||||
msg: El puerto SMTP debe ser el número 1 ~ 65535.
|
||||
text: El puerto a su servidor de correo.
|
||||
smtp_username:
|
||||
label: SMTP Username
|
||||
label: Nombre de usuario SMTP
|
||||
msg: SMTP username cannot be empty.
|
||||
smtp_password:
|
||||
label: SMTP Password
|
||||
msg: SMTP password cannot be empty.
|
||||
label: Contraseña SMTP
|
||||
msg: La contraseña SMTP no puede estar vacía.
|
||||
test_email_recipient:
|
||||
label: Test Email Recipients
|
||||
text: Provide email address that will receive test sends.
|
||||
msg: Test email recipients is invalid
|
||||
label: Destinatarios de correo electrónico de prueba
|
||||
text: Proporcione la dirección de correo electrónico que recibirá los envíos de prueba.
|
||||
msg: Los destinatarios de correo electrónico de prueba no son válidos
|
||||
smtp_authentication:
|
||||
label: Enable authentication
|
||||
title: SMTP Authentication
|
||||
msg: SMTP authentication cannot be empty.
|
||||
"yes": "Yes"
|
||||
label: Habilitar autenticación
|
||||
title: Autenticación SMTP
|
||||
msg: La autenticación SMTP no puede estar vacía.
|
||||
"yes": "Si"
|
||||
"no": "No"
|
||||
branding:
|
||||
page_title: Branding
|
||||
page_title: Marca
|
||||
logo:
|
||||
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.
|
||||
msg: El logotipo no puede estar vacío.
|
||||
text: La imagen del logotipo en la parte superior izquierda de su sitio. Utilice una imagen rectangular ancha con una altura de 56 y una relación de aspecto superior a 3:1. Si se deja en blanco, se mostrará el texto del título del sitio.
|
||||
mobile_logo:
|
||||
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.
|
||||
label: Logotipo móvil
|
||||
text: El logotipo utilizado en la versión móvil de su sitio. Utilice una imagen rectangular ancha con una altura de 56. Si se deja en blanco, se utilizará la imagen de la configuración de "logotipo".
|
||||
square_icon:
|
||||
label: Square Icon
|
||||
msg: Square icon cannot be empty.
|
||||
text: Image used as the base for metadata icons. Should ideally be larger than 512x512.
|
||||
label: Icono cuadrado
|
||||
msg: El icono cuadrado no puede estar vacío.
|
||||
text: Imagen utilizada como base para los iconos de metadatos. Idealmente, debería ser más grande que 512x512.
|
||||
favicon:
|
||||
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.
|
||||
label: Icono de favoritos
|
||||
text: Un favicon para su sitio. Para que funcione correctamente sobre un CDN, debe ser un png. Se cambiará el tamaño a 32x32. Si se deja en blanco, se utilizará el "icono cuadrado".
|
||||
legal:
|
||||
page_title: Legal
|
||||
terms_of_service:
|
||||
label: Terms of Service
|
||||
text: "You can add terms of service content here. If you already have a document hosted elsewhere, provide the full URL here."
|
||||
label: Términos de servicio
|
||||
text: "Puede agregar términos de contenido de servicio aquí. Si ya tiene un documento alojado en otro lugar, proporcione la URL completa aquí."
|
||||
privacy_policy:
|
||||
label: Privacy Policy
|
||||
text: "You can add privacy policy content here. If you already have a document hosted elsewhere, provide the full URL here."
|
||||
label: Política de privacidad
|
||||
text: "Puede agregar contenido de política de privacidad aquí. Si ya tiene un documento alojado en otro lugar, proporcione la URL completa aquí."
|
||||
write:
|
||||
page_title: Write
|
||||
page_title: Escribir
|
||||
recommend_tags:
|
||||
label: Recommend Tags
|
||||
text: "Please input tag slug above, one tag per line."
|
||||
label: Recomendar etiquetas
|
||||
text: "Ingrese el slug de la etiqueta arriba, una etiqueta por línea."
|
||||
required_tag:
|
||||
title: Required Tag
|
||||
label: Set recommend tag as required
|
||||
text: "Every new question must have at least one recommend tag."
|
||||
title: Etiqueta requerida
|
||||
label: Establezca la etiqueta de recomendación según sea necesario
|
||||
text: "Cada nueva pregunta debe tener al menos una etiqueta de recomendación."
|
||||
reserved_tags:
|
||||
label: Reserved Tags
|
||||
text: "Reserved tags can only be added to a post by moderator."
|
||||
label: Etiquetas reservadas
|
||||
text: "Solo el moderador puede agregar etiquetas reservadas a una publicación."
|
||||
seo:
|
||||
page_title: SEO
|
||||
permalink:
|
||||
label: Permalink
|
||||
text: Custom URL structures can improve the usability, and forward-compatibility of your links.
|
||||
label: Enlace permanente
|
||||
text: Las estructuras de URL personalizadas pueden mejorar la facilidad de uso y la compatibilidad futura de sus enlaces.
|
||||
robots:
|
||||
label: robots.txt
|
||||
text: This will permanently override any related site settings.
|
||||
text: Esto anulará permanentemente cualquier configuración del sitio relacionada.
|
||||
themes:
|
||||
page_title: Themes
|
||||
page_title: Temas
|
||||
themes:
|
||||
label: Themes
|
||||
text: Select an existing theme.
|
||||
label: Temas
|
||||
text: Seleccione un tema existente.
|
||||
navbar_style:
|
||||
label: Navbar Style
|
||||
text: Select an existing theme.
|
||||
label: Estilo de la barra de navegación
|
||||
text: Seleccione un tema existente.
|
||||
primary_color:
|
||||
label: Color principal
|
||||
text: Modify the colors used by your themes
|
||||
text: Modifica los colores usados por tus temas
|
||||
css_and_html:
|
||||
page_title: CSS y HTML
|
||||
custom_css:
|
||||
label: CSS personalizado
|
||||
text: Esto insertará como <link>
|
||||
head:
|
||||
label: Head
|
||||
label: Cabeza
|
||||
text: Esto se insertará antes de </head>
|
||||
header:
|
||||
label: Encabezado
|
||||
|
@ -1398,30 +1410,34 @@ ui:
|
|||
timeline:
|
||||
undeleted: recuperado
|
||||
deleted: eliminado
|
||||
downvote: downvote
|
||||
upvote: upvote
|
||||
accept: accept
|
||||
downvote: voto negativo
|
||||
upvote: votar a favor
|
||||
accept: aceptar
|
||||
cancelled: cancelado
|
||||
commented: comentado
|
||||
rollback: rollback
|
||||
edited: edited
|
||||
answered: answered
|
||||
asked: asked
|
||||
rollback: retroceder
|
||||
edited: editada
|
||||
answered: contestada
|
||||
asked: preguntó
|
||||
closed: cerrado
|
||||
reopened: reabierto
|
||||
created: creado
|
||||
pin: pinned
|
||||
unpin: unpinned
|
||||
show: listed
|
||||
hide: unlisted
|
||||
title: "Historial para"
|
||||
tag_title: "Línea temporal para"
|
||||
show_votes: "Mostrar votos"
|
||||
n_or_a: N/A
|
||||
title_for_question: "Timeline for"
|
||||
title_for_answer: "Timeline for answer to {{ title }} by {{ author }}"
|
||||
title_for_tag: "Timeline for tag"
|
||||
datetime: Datetime
|
||||
type: Type
|
||||
by: By
|
||||
title_for_question: "Línea de tiempo para"
|
||||
title_for_answer: "Cronología de la respuesta a {{ title }} por {{ author }}"
|
||||
title_for_tag: "Cronología de la etiqueta"
|
||||
datetime: Fecha y hora
|
||||
type: Tipo
|
||||
by: Por
|
||||
comment: Comentario
|
||||
no_data: "We couldn't find anything."
|
||||
no_data: "No pudimos encontrar nada."
|
||||
users:
|
||||
title: Usuarios
|
||||
users_with_the_most_reputation: Users with the highest reputation scores this week
|
||||
|
@ -1430,10 +1446,14 @@ ui:
|
|||
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.
|
||||
leave_page: '¿Seguro que quieres salir de la página?'
|
||||
changes_not_save: Es posible que sus cambios no se guarden.
|
||||
draft:
|
||||
discard_confirm: Are you sure you want to discard your draft?
|
||||
discard_confirm: '¿Está seguro de que desea descartar este borrador?'
|
||||
messages:
|
||||
post_deleted: This post has been deleted.
|
||||
|
||||
post_deleted: Esta publicación ha sido eliminada.
|
||||
post_pin: This post has been pinned.
|
||||
post_unpin: This post has been unpinned.
|
||||
post_hide_list: This post has been hidden from list.
|
||||
post_show_list: This post has been shown to list.
|
||||
post_reopen: This post has been reopened.
|
||||
|
|
|
@ -22,6 +22,14 @@ backend:
|
|||
other: Close
|
||||
reopen:
|
||||
other: Reopen
|
||||
pin:
|
||||
other: Pin
|
||||
hide:
|
||||
other: Unlist
|
||||
unpin:
|
||||
other: Unpin
|
||||
show:
|
||||
other: List
|
||||
role:
|
||||
name:
|
||||
user:
|
||||
|
@ -759,6 +767,7 @@ ui:
|
|||
btn: Ajouter une question
|
||||
answers: réponses
|
||||
question_detail:
|
||||
action: Action
|
||||
Asked: Demandé
|
||||
asked: demandé
|
||||
update: Modifié
|
||||
|
@ -800,7 +809,10 @@ ui:
|
|||
confirm_btn: Reopen
|
||||
title: Rouvrir ce message
|
||||
content: Êtes-vous sûr de vouloir rouvrir ?
|
||||
success: Ce message a été rouvert
|
||||
pin:
|
||||
title: Pin this post
|
||||
content: Are you sure you wish to pinned globally? This post will appear at the top of all post lists.
|
||||
confirm_btn: Pin
|
||||
delete:
|
||||
title: Supprimer la publication
|
||||
question: >-
|
||||
|
@ -808,7 +820,6 @@ ui:
|
|||
answer_accepted: >-
|
||||
<p>Nous ne recommandons pas <strong>de supprimer la réponse acceptée</strong> car cela prive les futurs lecteurs de cette connaissance. </p> La suppression répétée des réponses acceptées peut empêcher votre compte de répondre. Êtes-vous sûr de vouloir supprimer ?
|
||||
other: Êtes-vous sûr de vouloir supprimer ?
|
||||
tip_question_deleted: Ce message a été supprimé
|
||||
tip_answer_deleted: Cette réponse a été supprimée
|
||||
btns:
|
||||
confirm: Confimer
|
||||
|
@ -824,6 +835,7 @@ ui:
|
|||
reject: Rejeter
|
||||
skip: Ignorer
|
||||
discard_draft: Abandonner le brouillon
|
||||
pinned: Pinned
|
||||
search:
|
||||
title: Résultats de la recherche
|
||||
keywords: Mots-clés
|
||||
|
@ -1385,6 +1397,10 @@ ui:
|
|||
closed: fermé
|
||||
reopened: réouvert
|
||||
created: créé
|
||||
pin: pinned
|
||||
unpin: unpinned
|
||||
show: listed
|
||||
hide: unlisted
|
||||
title: "Historique de"
|
||||
tag_title: "Chronologie de"
|
||||
show_votes: "Afficher les votes"
|
||||
|
@ -1411,4 +1427,8 @@ ui:
|
|||
discard_confirm: Êtes-vous sûr de vouloir abandonner ce brouillon ?
|
||||
messages:
|
||||
post_deleted: Ce message a été supprimé.
|
||||
|
||||
post_pin: This post has been pinned.
|
||||
post_unpin: This post has been unpinned.
|
||||
post_hide_list: This post has been hidden from list.
|
||||
post_show_list: This post has been shown to list.
|
||||
post_reopen: This post has been reopened.
|
||||
|
|
|
@ -22,6 +22,14 @@ backend:
|
|||
other: Close
|
||||
reopen:
|
||||
other: Reopen
|
||||
pin:
|
||||
other: Pin
|
||||
hide:
|
||||
other: Unlist
|
||||
unpin:
|
||||
other: Unpin
|
||||
show:
|
||||
other: List
|
||||
role:
|
||||
name:
|
||||
user:
|
||||
|
@ -759,6 +767,7 @@ ui:
|
|||
btn: Tambahkan pertanyaan
|
||||
answers: jawaban
|
||||
question_detail:
|
||||
action: Action
|
||||
Asked: Ditanyakan
|
||||
asked: ditanyakan
|
||||
update: Diubah
|
||||
|
@ -800,7 +809,10 @@ ui:
|
|||
confirm_btn: Reopen
|
||||
title: Buka kembali postingan ini
|
||||
content: Kamu yakin ingin membuka kembali?
|
||||
success: Postingan ini telah dibuka kembali
|
||||
pin:
|
||||
title: Pin this post
|
||||
content: Are you sure you wish to pinned globally? This post will appear at the top of all post lists.
|
||||
confirm_btn: Pin
|
||||
delete:
|
||||
title: Hapus pos ini
|
||||
question: >-
|
||||
|
@ -808,7 +820,6 @@ ui:
|
|||
answer_accepted: >-
|
||||
<p>Kami tidak menyarankan <strong>menghapus jawaban yang diterima</strong> karena hal itu menghilangkan pengetahuan ini dari pembaca di masa mendatang. </p> Penghapusan berulang dari jawaban yang diterima dapat menyebabkan akun Anda diblokir dari menjawab. Apakah Anda yakin ingin menghapus?
|
||||
other: Anda yakin ingin menghapusnya?
|
||||
tip_question_deleted: Kiriman ini sudah dihapus
|
||||
tip_answer_deleted: Jawaban ini telah dihapus
|
||||
btns:
|
||||
confirm: Konfirmasi
|
||||
|
@ -824,6 +835,7 @@ ui:
|
|||
reject: Reject
|
||||
skip: Skip
|
||||
discard_draft: Discard draft
|
||||
pinned: Pinned
|
||||
search:
|
||||
title: Search Results
|
||||
keywords: Keywords
|
||||
|
@ -1385,6 +1397,10 @@ ui:
|
|||
closed: closed
|
||||
reopened: reopened
|
||||
created: created
|
||||
pin: pinned
|
||||
unpin: unpinned
|
||||
show: listed
|
||||
hide: unlisted
|
||||
title: "History for"
|
||||
tag_title: "Timeline for"
|
||||
show_votes: "Show votes"
|
||||
|
@ -1411,4 +1427,8 @@ ui:
|
|||
discard_confirm: Are you sure you want to discard your draft?
|
||||
messages:
|
||||
post_deleted: This post has been deleted.
|
||||
|
||||
post_pin: This post has been pinned.
|
||||
post_unpin: This post has been unpinned.
|
||||
post_hide_list: This post has been hidden from list.
|
||||
post_show_list: This post has been shown to list.
|
||||
post_reopen: This post has been reopened.
|
||||
|
|
|
@ -22,6 +22,14 @@ backend:
|
|||
other: Close
|
||||
reopen:
|
||||
other: Reopen
|
||||
pin:
|
||||
other: Pin
|
||||
hide:
|
||||
other: Unlist
|
||||
unpin:
|
||||
other: Unpin
|
||||
show:
|
||||
other: List
|
||||
role:
|
||||
name:
|
||||
user:
|
||||
|
@ -38,9 +46,9 @@ backend:
|
|||
moderator:
|
||||
other: Ha accesso a tutti i post tranne le impostazioni di amministratore.
|
||||
email:
|
||||
other: email
|
||||
other: E-mail
|
||||
password:
|
||||
other: password
|
||||
other: Chiave di accesso
|
||||
email_or_password_wrong_error:
|
||||
other: Email o password errati
|
||||
error:
|
||||
|
@ -445,7 +453,7 @@ ui:
|
|||
range: Display name up to 35 characters.
|
||||
slug_name:
|
||||
label: URL Slug
|
||||
desc: 'Must use the character set "a-z", "0-9", "+ # - ."'
|
||||
desc: URL slug fino a 35 caratteri.
|
||||
msg:
|
||||
empty: URL slug cannot be empty.
|
||||
range: URL slug up to 35 characters.
|
||||
|
@ -759,6 +767,7 @@ ui:
|
|||
btn: Add question
|
||||
answers: answers
|
||||
question_detail:
|
||||
action: Action
|
||||
Asked: Asked
|
||||
asked: asked
|
||||
update: Modified
|
||||
|
@ -800,7 +809,10 @@ ui:
|
|||
confirm_btn: Reopen
|
||||
title: Reopen this post
|
||||
content: Are you sure you want to reopen?
|
||||
success: This post has been reopened
|
||||
pin:
|
||||
title: Pin this post
|
||||
content: Are you sure you wish to pinned globally? This post will appear at the top of all post lists.
|
||||
confirm_btn: Pin
|
||||
delete:
|
||||
title: Delete this post
|
||||
question: >-
|
||||
|
@ -808,7 +820,6 @@ ui:
|
|||
answer_accepted: >-
|
||||
<p>We do not recommend <strong>deleting accepted answer</strong> because doing so deprives future readers of this knowledge. </p> Repeated deletion of accepted answers can result in your account being blocked from answering. Are you sure you wish to delete?
|
||||
other: Are you sure you wish to delete?
|
||||
tip_question_deleted: This post has been deleted
|
||||
tip_answer_deleted: This answer has been deleted
|
||||
btns:
|
||||
confirm: Confirm
|
||||
|
@ -824,6 +835,7 @@ ui:
|
|||
reject: Reject
|
||||
skip: Skip
|
||||
discard_draft: Discard draft
|
||||
pinned: Pinned
|
||||
search:
|
||||
title: Search Results
|
||||
keywords: Keywords
|
||||
|
@ -1385,6 +1397,10 @@ ui:
|
|||
closed: closed
|
||||
reopened: reopened
|
||||
created: created
|
||||
pin: pinned
|
||||
unpin: unpinned
|
||||
show: listed
|
||||
hide: unlisted
|
||||
title: "History for"
|
||||
tag_title: "Timeline for"
|
||||
show_votes: "Show votes"
|
||||
|
@ -1411,4 +1427,8 @@ ui:
|
|||
discard_confirm: Are you sure you want to discard your draft?
|
||||
messages:
|
||||
post_deleted: This post has been deleted.
|
||||
|
||||
post_pin: This post has been pinned.
|
||||
post_unpin: This post has been unpinned.
|
||||
post_hide_list: This post has been hidden from list.
|
||||
post_show_list: This post has been shown to list.
|
||||
post_reopen: This post has been reopened.
|
||||
|
|
440
i18n/ja_JP.yaml
440
i18n/ja_JP.yaml
|
@ -13,15 +13,23 @@ backend:
|
|||
other: データサーバーエラー
|
||||
action:
|
||||
report:
|
||||
other: Flag
|
||||
other: フラグ
|
||||
edit:
|
||||
other: Edit
|
||||
other: 編集
|
||||
delete:
|
||||
other: Delete
|
||||
other: 削除
|
||||
close:
|
||||
other: Close
|
||||
other: 閉じる
|
||||
reopen:
|
||||
other: Reopen
|
||||
pin:
|
||||
other: Pin
|
||||
hide:
|
||||
other: Unlist
|
||||
unpin:
|
||||
other: Unpin
|
||||
show:
|
||||
other: List
|
||||
role:
|
||||
name:
|
||||
user:
|
||||
|
@ -53,7 +61,7 @@ backend:
|
|||
other: メールアドレスとパスワードが一致しません。
|
||||
answer:
|
||||
not_found:
|
||||
other: Answer do not found.
|
||||
other: 回答が見つかりません。
|
||||
cannot_deleted:
|
||||
other: 削除する権限がありません。
|
||||
cannot_update:
|
||||
|
@ -139,7 +147,7 @@ backend:
|
|||
other: The From Name cannot be a email address.
|
||||
theme:
|
||||
not_found:
|
||||
other: Theme not found.
|
||||
other: テーマが見つかりません。
|
||||
revision:
|
||||
review_underway:
|
||||
other: Can't edit currently, there is a version in the review queue.
|
||||
|
@ -148,9 +156,9 @@ backend:
|
|||
user:
|
||||
email_or_password_wrong:
|
||||
other:
|
||||
other: Email and password do not match.
|
||||
other: メールアドレスとパスワードが一致しません。
|
||||
not_found:
|
||||
other: User not found.
|
||||
other: ユーザーが見つかりません。
|
||||
suspended:
|
||||
other: User has been suspended.
|
||||
username_invalid:
|
||||
|
@ -170,7 +178,7 @@ backend:
|
|||
connection_failed:
|
||||
other: Database connection failed
|
||||
create_table_failed:
|
||||
other: Create table failed
|
||||
other: テーブルの作成に失敗しました
|
||||
install:
|
||||
create_config_failed:
|
||||
other: Can't create the config.yaml file.
|
||||
|
@ -180,7 +188,7 @@ backend:
|
|||
report:
|
||||
spam:
|
||||
name:
|
||||
other: spam
|
||||
other: スパム
|
||||
desc:
|
||||
other: This post is an advertisement, or vandalism. It is not useful or relevant to the current topic.
|
||||
rude:
|
||||
|
@ -212,7 +220,7 @@ backend:
|
|||
close:
|
||||
duplicate:
|
||||
name:
|
||||
other: spam
|
||||
other: スパム
|
||||
desc:
|
||||
other: This question has been asked before and already has an answer.
|
||||
guideline:
|
||||
|
@ -232,11 +240,11 @@ backend:
|
|||
other: This post requires another reason not listed above.
|
||||
operation_type:
|
||||
asked:
|
||||
other: asked
|
||||
other: 質問済み
|
||||
answered:
|
||||
other: answered
|
||||
other: 回答済み
|
||||
modified:
|
||||
other: modified
|
||||
other: 修正済み
|
||||
notification:
|
||||
action:
|
||||
update_question:
|
||||
|
@ -270,48 +278,48 @@ ui:
|
|||
desc: >-
|
||||
<ul class="mb-0"><li><p class="mb-2">to make links</p><pre class="mb-2"><code><https://url.com><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>></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: 前へ
|
||||
next: 次へ
|
||||
page_title:
|
||||
question: Question
|
||||
questions: Questions
|
||||
tag: Tag
|
||||
tags: Tags
|
||||
question: 質問
|
||||
questions: 質問
|
||||
tag: タグ
|
||||
tags: タグ
|
||||
tag_wiki: tag wiki
|
||||
create_tag: Create Tag
|
||||
edit_tag: Edit Tag
|
||||
ask_a_question: Add Question
|
||||
edit_question: Edit Question
|
||||
edit_answer: Edit Answer
|
||||
search: Search
|
||||
create_tag: タグを作成
|
||||
edit_tag: タグを編集
|
||||
ask_a_question: 質問を追加
|
||||
edit_question: 質問を編集
|
||||
edit_answer: 回答を編集
|
||||
search: 検索
|
||||
posts_containing: Posts containing
|
||||
settings: Settings
|
||||
settings: 設定
|
||||
notifications: Notifications
|
||||
login: Log In
|
||||
sign_up: Sign Up
|
||||
account_recovery: Account Recovery
|
||||
login: ログイン
|
||||
sign_up: 新規登録
|
||||
account_recovery: アカウントの復旧
|
||||
account_activation: Account Activation
|
||||
confirm_email: Confirm Email
|
||||
confirm_email: メールアドレスを確認
|
||||
account_suspended: Account Suspended
|
||||
admin: Admin
|
||||
change_email: Modify Email
|
||||
admin: 管理者
|
||||
change_email: メールアドレスを変更
|
||||
install: Answer Installation
|
||||
upgrade: Answer Upgrade
|
||||
maintenance: Website Maintenance
|
||||
users: Users
|
||||
http_404: HTTP Error 404
|
||||
http_50X: HTTP Error 500
|
||||
http_403: HTTP Error 403
|
||||
users: ユーザー
|
||||
http_404: HTTP エラー 404
|
||||
http_50X: HTTP エラー 500
|
||||
http_403: HTTP エラー 403
|
||||
notifications:
|
||||
title: Notifications
|
||||
inbox: Inbox
|
||||
title: 通知
|
||||
inbox: 受信トレイ
|
||||
achievement: Achievements
|
||||
all_read: Mark all as read
|
||||
show_more: Show more
|
||||
show_more: もっと見る
|
||||
suspended:
|
||||
title: Your Account has been Suspended
|
||||
until_time: "Your account was suspended until {{ time }}."
|
||||
forever: This user was suspended forever.
|
||||
title: あなたのアカウントは停止されています。
|
||||
until_time: "あなたのアカウントは {{ time }} まで停止されました。"
|
||||
forever: このユーザーは永久に停止されました。
|
||||
end: You don't meet a community guideline.
|
||||
editor:
|
||||
blockquote:
|
||||
|
@ -401,83 +409,83 @@ ui:
|
|||
url:
|
||||
label: URL
|
||||
msg:
|
||||
empty: URL cannot be empty.
|
||||
empty: URLを入力してください。
|
||||
name:
|
||||
label: Description
|
||||
btn_cancel: Cancel
|
||||
btn_confirm: Add
|
||||
label: 説明
|
||||
btn_cancel: キャンセル
|
||||
btn_confirm: 追加
|
||||
ordered_list:
|
||||
text: Numbered List
|
||||
text: 番号付きリスト
|
||||
unordered_list:
|
||||
text: Bulleted List
|
||||
text: 箇条書きリスト
|
||||
table:
|
||||
text: Table
|
||||
heading: Heading
|
||||
cell: Cell
|
||||
text: ' テーブル'
|
||||
heading: 見出し
|
||||
cell: セル
|
||||
close_modal:
|
||||
title: I am closing this post as...
|
||||
btn_cancel: Cancel
|
||||
btn_submit: Submit
|
||||
btn_cancel: キャンセル
|
||||
btn_submit: 送信
|
||||
remark:
|
||||
empty: Cannot be empty.
|
||||
empty: 入力してください。
|
||||
msg:
|
||||
empty: Please select a reason.
|
||||
empty: 理由を選んでください。
|
||||
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: キャンセル
|
||||
btn_submit: 送信
|
||||
remark:
|
||||
empty: Cannot be empty.
|
||||
empty: 入力してください。
|
||||
msg:
|
||||
empty: Please select a reason.
|
||||
empty: 理由を選んでください。
|
||||
tag_modal:
|
||||
title: Create new tag
|
||||
title: 新しいタグを作成
|
||||
form:
|
||||
fields:
|
||||
display_name:
|
||||
label: Display Name
|
||||
label: 表示名
|
||||
msg:
|
||||
empty: Display name cannot be empty.
|
||||
range: Display name up to 35 characters.
|
||||
empty: 表示名を入力してください。
|
||||
range: 表示名は最大 35 文字までです。
|
||||
slug_name:
|
||||
label: URL Slug
|
||||
label: URLスラッグ
|
||||
desc: '文字セット「a-z」、「0-9」、「+ # -」を使用する必要があります。'
|
||||
msg:
|
||||
empty: URL slug cannot be empty.
|
||||
empty: URL スラッグを空にすることはできません。
|
||||
range: URL slug up to 35 characters.
|
||||
character: URL slug contains unallowed character set.
|
||||
desc:
|
||||
label: Description
|
||||
btn_cancel: Cancel
|
||||
btn_submit: Submit
|
||||
btn_post: Post new tag
|
||||
label: 説明
|
||||
btn_cancel: キャンセル
|
||||
btn_submit: 送信
|
||||
btn_post: 新しいタグを投稿
|
||||
tag_info:
|
||||
created_at: Created
|
||||
edited_at: Edited
|
||||
history: History
|
||||
created_at: 作成
|
||||
edited_at: 編集済
|
||||
history: 履歴
|
||||
synonyms:
|
||||
title: Synonyms
|
||||
title: 類義語
|
||||
text: The following tags will be remapped to
|
||||
empty: No synonyms found.
|
||||
btn_add: Add a synonym
|
||||
btn_edit: Edit
|
||||
btn_save: Save
|
||||
btn_edit: 編集
|
||||
btn_save: 保存
|
||||
synonyms_text: The following tags will be remapped to
|
||||
delete:
|
||||
title: Delete this tag
|
||||
title: このタグを削除
|
||||
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
|
||||
close: 閉じる
|
||||
edit_tag:
|
||||
title: Edit Tag
|
||||
default_reason: Edit tag
|
||||
title: タグを編集
|
||||
default_reason: タグを編集
|
||||
form:
|
||||
fields:
|
||||
revision:
|
||||
|
@ -621,43 +629,43 @@ ui:
|
|||
another: >-
|
||||
We sent another activation email to you at <bold>{{mail}}</bold>. It might take a few minutes for it to arrive; be sure to check your spam folder.
|
||||
btn_name: Resend activation email
|
||||
change_btn_name: Change email
|
||||
change_btn_name: メールアドレスを変更
|
||||
msg:
|
||||
empty: Cannot be empty.
|
||||
login:
|
||||
page_title: Welcome to {{site_name}}
|
||||
page_title: '{{site_name}} へようこそ'
|
||||
login_to_continue: Log in to continue
|
||||
info_sign: Don't have an account? <1>Sign up</1>
|
||||
info_login: Already have an account? <1>Log in</1>
|
||||
agreements: By registering, you agree to the <1>privacy policy</1> and <3>terms of service</3>.
|
||||
forgot_pass: Forgot password?
|
||||
forgot_pass: パスワードをお忘れですか?
|
||||
name:
|
||||
label: Name
|
||||
msg:
|
||||
empty: Name cannot be empty.
|
||||
range: Name up to 30 characters.
|
||||
email:
|
||||
label: Email
|
||||
label: メールアドレス
|
||||
msg:
|
||||
empty: Email cannot be empty.
|
||||
empty: メールアドレスを入力してください。
|
||||
password:
|
||||
label: Password
|
||||
label: パスワード
|
||||
msg:
|
||||
empty: Password cannot be empty.
|
||||
empty: パスワードを入力してください。
|
||||
different: The passwords entered on both sides are inconsistent
|
||||
account_forgot:
|
||||
page_title: Forgot Your Password
|
||||
page_title: パスワードを忘れた方はこちら
|
||||
btn_name: Send me recovery email
|
||||
send_success: >-
|
||||
If an account matches <strong>{{mail}}</strong>, you should receive an email with instructions on how to reset your password shortly.
|
||||
email:
|
||||
label: Email
|
||||
label: メールアドレス
|
||||
msg:
|
||||
empty: Email cannot be empty.
|
||||
empty: メールアドレスを入力してください。
|
||||
change_email:
|
||||
page_title: Welcome to {{site_name}}
|
||||
btn_cancel: Cancel
|
||||
btn_update: Update email address
|
||||
page_title: '{{site_name}} へようこそ'
|
||||
btn_cancel: キャンセル
|
||||
btn_update: メールアドレスを更新
|
||||
send_success: >-
|
||||
If an account matches <strong>{{mail}}</strong>, you should receive an email with instructions on how to reset your password shortly.
|
||||
email:
|
||||
|
@ -665,17 +673,17 @@ ui:
|
|||
msg:
|
||||
empty: Email cannot be empty.
|
||||
password_reset:
|
||||
page_title: Password Reset
|
||||
btn_name: Reset my password
|
||||
page_title: パスワード再設定
|
||||
btn_name: パスワードをリセット
|
||||
reset_success: >-
|
||||
You successfully changed your password; you will be redirected to the log in page.
|
||||
link_invalid: >-
|
||||
Sorry, this password reset link is no longer valid. Perhaps your password is already reset?
|
||||
to_login: Continue to log in page
|
||||
password:
|
||||
label: Password
|
||||
label: パスワード
|
||||
msg:
|
||||
empty: Password cannot be empty.
|
||||
empty: パスワードを入力してください。
|
||||
length: The length needs to be between 8 and 32
|
||||
different: The passwords entered on both sides are inconsistent
|
||||
password_confirm:
|
||||
|
@ -712,8 +720,8 @@ ui:
|
|||
bio:
|
||||
label: About Me
|
||||
website:
|
||||
label: Website
|
||||
placeholder: "https://example.com"
|
||||
label: ウェブサイト
|
||||
placeholder: "http://example.com"
|
||||
msg: Website incorrect format
|
||||
location:
|
||||
label: Location
|
||||
|
@ -740,7 +748,7 @@ ui:
|
|||
length: The length needs to be between 8 and 32.
|
||||
different: The two entered passwords do not match.
|
||||
new_pass:
|
||||
label: New Password
|
||||
label: 新しいパスワード
|
||||
pass_confirm:
|
||||
label: Confirm New Password
|
||||
interface:
|
||||
|
@ -759,6 +767,7 @@ ui:
|
|||
btn: Add question
|
||||
answers: answers
|
||||
question_detail:
|
||||
action: Action
|
||||
Asked: Asked
|
||||
asked: asked
|
||||
update: Modified
|
||||
|
@ -775,10 +784,10 @@ ui:
|
|||
answer_useful: It is useful
|
||||
answer_un_useful: It is not useful
|
||||
answers:
|
||||
title: Answers
|
||||
score: Score
|
||||
newest: Newest
|
||||
btn_accept: Accept
|
||||
title: 回答
|
||||
score: スコア
|
||||
newest: 最新
|
||||
btn_accept: 承認
|
||||
btn_accepted: Accepted
|
||||
write_answer:
|
||||
title: Your Answer
|
||||
|
@ -800,7 +809,10 @@ ui:
|
|||
confirm_btn: Reopen
|
||||
title: Reopen this post
|
||||
content: Are you sure you want to reopen?
|
||||
success: This post has been reopened
|
||||
pin:
|
||||
title: Pin this post
|
||||
content: Are you sure you wish to pinned globally? This post will appear at the top of all post lists.
|
||||
confirm_btn: Pin
|
||||
delete:
|
||||
title: Delete this post
|
||||
question: >-
|
||||
|
@ -808,7 +820,6 @@ ui:
|
|||
answer_accepted: >-
|
||||
<p>We do not recommend <strong>deleting accepted answer</strong> because doing so deprives future readers of this knowledge. </p> Repeated deletion of accepted answers can result in your account being blocked from answering. Are you sure you wish to delete?
|
||||
other: Are you sure you wish to delete?
|
||||
tip_question_deleted: This post has been deleted
|
||||
tip_answer_deleted: This answer has been deleted
|
||||
btns:
|
||||
confirm: Confirm
|
||||
|
@ -816,7 +827,7 @@ ui:
|
|||
save: Save
|
||||
delete: Delete
|
||||
login: Log in
|
||||
signup: Sign up
|
||||
signup: 新規登録
|
||||
logout: Log out
|
||||
verify: Verify
|
||||
add_question: Add question
|
||||
|
@ -824,6 +835,7 @@ ui:
|
|||
reject: Reject
|
||||
skip: Skip
|
||||
discard_draft: Discard draft
|
||||
pinned: Pinned
|
||||
search:
|
||||
title: Search Results
|
||||
keywords: Keywords
|
||||
|
@ -848,17 +860,17 @@ ui:
|
|||
is_answer: "<1>is:answer</1> search answers"
|
||||
empty: We couldn't find anything. <br /> Try different or less specific keywords.
|
||||
share:
|
||||
name: Share
|
||||
copy: Copy link
|
||||
name: シェア
|
||||
copy: リンクをコピー
|
||||
via: Share post via...
|
||||
copied: Copied
|
||||
facebook: Share to Facebook
|
||||
twitter: Share to Twitter
|
||||
facebook: Facebookで共有
|
||||
twitter: Twitterで共有
|
||||
cannot_vote_for_self: You can't vote for your own post
|
||||
modal_confirm:
|
||||
title: Error...
|
||||
account_result:
|
||||
page_title: Welcome to {{site_name}}
|
||||
page_title: '{{site_name}} へようこそ'
|
||||
success: Your new account is confirmed; you will be redirected to the home page.
|
||||
link: Continue to homepage
|
||||
invalid: >-
|
||||
|
@ -880,24 +892,24 @@ ui:
|
|||
all_questions: All Questions
|
||||
x_questions: "{{ count }} Questions"
|
||||
x_answers: "{{ count }} answers"
|
||||
questions: Questions
|
||||
answers: Answers
|
||||
newest: Newest
|
||||
active: Active
|
||||
frequent: Frequent
|
||||
score: Score
|
||||
unanswered: Unanswered
|
||||
modified: modified
|
||||
answered: answered
|
||||
asked: asked
|
||||
closed: closed
|
||||
follow_a_tag: Follow a tag
|
||||
more: More
|
||||
questions: 質問
|
||||
answers: 回答
|
||||
newest: 最新
|
||||
active: 有効
|
||||
frequent: 頻繁
|
||||
score: スコア
|
||||
unanswered: 未回答
|
||||
modified: 修正済み
|
||||
answered: 回答済み
|
||||
asked: 質問済み
|
||||
closed: 終了
|
||||
follow_a_tag: タグをフォロー
|
||||
more: その他
|
||||
personal:
|
||||
overview: Overview
|
||||
answers: Answers
|
||||
answer: answer
|
||||
questions: Questions
|
||||
overview: 概要
|
||||
answers: 回答
|
||||
answer: 回答
|
||||
questions: 質問
|
||||
question: question
|
||||
bookmarks: Bookmarks
|
||||
reputation: Reputation
|
||||
|
@ -918,32 +930,32 @@ ui:
|
|||
list_empty: No posts found.<br />Perhaps you'd like to select a different tab?
|
||||
accepted: Accepted
|
||||
answered: answered
|
||||
asked: asked
|
||||
upvote: upvote
|
||||
downvote: downvote
|
||||
asked: 質問済み
|
||||
upvote: 賛成
|
||||
downvote: 反対票を投じる
|
||||
mod_short: Mod
|
||||
mod_long: Moderators
|
||||
mod_long: モデレーター
|
||||
x_reputation: reputation
|
||||
x_votes: votes received
|
||||
x_answers: answers
|
||||
x_questions: questions
|
||||
x_answers: 回答
|
||||
x_questions: 質問
|
||||
install:
|
||||
title: Answer
|
||||
next: Next
|
||||
done: Done
|
||||
title: 回答
|
||||
next: 次へ
|
||||
done: 完了
|
||||
config_yaml_error: Can't create the config.yaml file.
|
||||
lang:
|
||||
label: Please Choose a Language
|
||||
db_type:
|
||||
label: Database Engine
|
||||
db_username:
|
||||
label: Username
|
||||
label: ユーザー名
|
||||
placeholder: root
|
||||
msg: Username cannot be empty.
|
||||
db_password:
|
||||
label: Password
|
||||
label: パスワード
|
||||
placeholder: root
|
||||
msg: Password cannot be empty.
|
||||
msg: パスワードを入力してください。
|
||||
db_host:
|
||||
label: Database Host
|
||||
placeholder: "db:3306"
|
||||
|
@ -963,12 +975,12 @@ ui:
|
|||
You can create the <1>config.yaml</1> file manually in the <1>/var/wwww/xxx/</1> directory and paste the following text into it.
|
||||
info: After you've done that, click "Next" button.
|
||||
site_information: Site Information
|
||||
admin_account: Admin Account
|
||||
admin_account: 管理者アカウント
|
||||
site_name:
|
||||
label: Site Name
|
||||
msg: Site Name cannot be empty.
|
||||
label: サイト名
|
||||
msg: サイト名を入力してください。
|
||||
site_url:
|
||||
label: Site URL
|
||||
label: サイトURL
|
||||
text: The address of your site.
|
||||
msg:
|
||||
empty: Site URL cannot be empty.
|
||||
|
@ -983,12 +995,12 @@ ui:
|
|||
label: Name
|
||||
msg: Name cannot be empty.
|
||||
admin_password:
|
||||
label: Password
|
||||
label: パスワード
|
||||
text: >-
|
||||
You will need this password to log in. Please store it in a secure location.
|
||||
msg: Password cannot be empty.
|
||||
admin_email:
|
||||
label: Email
|
||||
label: メールアドレス
|
||||
text: You will need this email to log in.
|
||||
msg:
|
||||
empty: Email cannot be empty.
|
||||
|
@ -1026,10 +1038,10 @@ ui:
|
|||
questions: Questions
|
||||
answers: Answers
|
||||
users: Users
|
||||
flags: Flags
|
||||
settings: Settings
|
||||
general: General
|
||||
interface: Interface
|
||||
flags: フラグ
|
||||
settings: 設定
|
||||
general: 一般
|
||||
interface: 外観
|
||||
smtp: SMTP
|
||||
branding: Branding
|
||||
legal: Legal
|
||||
|
@ -1037,21 +1049,21 @@ ui:
|
|||
tos: Terms of Service
|
||||
privacy: Privacy
|
||||
seo: SEO
|
||||
customize: Customize
|
||||
themes: Themes
|
||||
customize: カスタマイズ
|
||||
themes: テーマ
|
||||
css-html: CSS/HTML
|
||||
login: Login
|
||||
login: ログイン
|
||||
admin:
|
||||
admin_header:
|
||||
title: Admin
|
||||
title: 管理者
|
||||
dashboard:
|
||||
title: Dashboard
|
||||
title: ダッシュボード
|
||||
welcome: Welcome to Answer Admin!
|
||||
site_statistics: Site Statistics
|
||||
questions: "Questions:"
|
||||
answers: "Answers:"
|
||||
comments: "Comments:"
|
||||
votes: "Votes:"
|
||||
site_statistics: サイト統計
|
||||
questions: "質問:"
|
||||
answers: "回答:"
|
||||
comments: "評論:"
|
||||
votes: "投票:"
|
||||
active_users: "Active users:"
|
||||
flags: "Flags:"
|
||||
site_health_status: Site Health Status
|
||||
|
@ -1065,15 +1077,15 @@ ui:
|
|||
uptime: "Uptime:"
|
||||
answer_links: Answer Links
|
||||
documents: Documents
|
||||
feedback: Feedback
|
||||
support: Support
|
||||
review: Review
|
||||
config: Config
|
||||
feedback: フィードバック
|
||||
support: サポート
|
||||
review: レビュー
|
||||
config: 設定
|
||||
update_to: Update to
|
||||
latest: Latest
|
||||
check_failed: Check failed
|
||||
"yes": "Yes"
|
||||
"no": "No"
|
||||
"yes": "はい"
|
||||
"no": "いいえ"
|
||||
not_allowed: Not allowed
|
||||
allowed: Allowed
|
||||
enabled: Enabled
|
||||
|
@ -1121,7 +1133,7 @@ ui:
|
|||
users:
|
||||
title: Users
|
||||
name: Name
|
||||
email: Email
|
||||
email: メールアドレス
|
||||
reputation: Reputation
|
||||
created_at: Created Time
|
||||
delete_at: Deleted Time
|
||||
|
@ -1136,51 +1148,51 @@ ui:
|
|||
suspended: Suspended
|
||||
deleted: Deleted
|
||||
normal: Normal
|
||||
Moderator: Moderator
|
||||
Admin: Admin
|
||||
User: User
|
||||
Moderator: モデレーター
|
||||
Admin: 管理者
|
||||
User: ユーザー
|
||||
filter:
|
||||
placeholder: "Filter by name, user:id"
|
||||
set_new_password: Set new password
|
||||
change_status: Change status
|
||||
change_role: Change role
|
||||
show_logs: Show logs
|
||||
add_user: Add user
|
||||
set_new_password: 新しいパスワードを設定します。
|
||||
change_status: ステータスを変更
|
||||
change_role: ロールを変更
|
||||
show_logs: ログを表示
|
||||
add_user: ユーザを追加
|
||||
new_password_modal:
|
||||
title: Set new password
|
||||
title: 新しいパスワードを設定
|
||||
form:
|
||||
fields:
|
||||
password:
|
||||
label: Password
|
||||
label: パスワード
|
||||
text: The user will be logged out and need to login again.
|
||||
msg: Password must be at 8-32 characters in length.
|
||||
btn_cancel: Cancel
|
||||
btn_submit: Submit
|
||||
btn_cancel: キャンセル
|
||||
btn_submit: 送信
|
||||
user_modal:
|
||||
title: Add new user
|
||||
title: 新しいユーザーを追加
|
||||
form:
|
||||
fields:
|
||||
display_name:
|
||||
label: Display Name
|
||||
msg: Display Name must be at 3-30 characters in length.
|
||||
label: 表示名
|
||||
msg: 表示名の長さは 3 ~ 30 文字にする必要があります。
|
||||
email:
|
||||
label: Email
|
||||
msg: Email is not valid.
|
||||
label: メールアドレス
|
||||
msg: メールアドレスが無効です。
|
||||
password:
|
||||
label: Password
|
||||
msg: Password must be at 8-32 characters in length.
|
||||
btn_cancel: Cancel
|
||||
btn_submit: Submit
|
||||
label: パスワード
|
||||
msg: パスワードの長さは 8 ~ 32 文字である必要があります。
|
||||
btn_cancel: キャンセル
|
||||
btn_submit: 送信
|
||||
questions:
|
||||
page_title: Questions
|
||||
normal: Normal
|
||||
closed: Closed
|
||||
deleted: Deleted
|
||||
post: Post
|
||||
votes: Votes
|
||||
answers: Answers
|
||||
created: Created
|
||||
status: Status
|
||||
page_title: 質問
|
||||
normal: 正常
|
||||
closed: 終了
|
||||
deleted: 削除済み
|
||||
post: 投稿
|
||||
votes: 投票
|
||||
answers: 回答
|
||||
created: 作成
|
||||
status: ステータス
|
||||
action: Action
|
||||
change: Change
|
||||
filter:
|
||||
|
@ -1204,7 +1216,7 @@ ui:
|
|||
msg: Site name cannot be empty.
|
||||
text: "The name of this site, as used in the title tag."
|
||||
site_url:
|
||||
label: Site URL
|
||||
label: サイトURL
|
||||
msg: Site url cannot be empty.
|
||||
validate: Please enter a valid URL.
|
||||
text: The address of your site.
|
||||
|
@ -1253,17 +1265,17 @@ ui:
|
|||
msg: Encryption cannot be empty.
|
||||
text: For most servers SSL is the recommended option.
|
||||
ssl: SSL
|
||||
none: None
|
||||
none: なし
|
||||
smtp_port:
|
||||
label: SMTP Port
|
||||
msg: SMTP port must be number 1 ~ 65535.
|
||||
label: SMTP ポート
|
||||
msg: SMTPポートは1〜65535でなければなりません。
|
||||
text: The port to your mail server.
|
||||
smtp_username:
|
||||
label: SMTP Username
|
||||
msg: SMTP username cannot be empty.
|
||||
label: SMTP ユーザー名
|
||||
msg: SMTP ユーザー名を空にすることはできません。
|
||||
smtp_password:
|
||||
label: SMTP Password
|
||||
msg: SMTP password cannot be empty.
|
||||
label: SMTP パスワード
|
||||
msg: SMTP パスワードを入力してください。
|
||||
test_email_recipient:
|
||||
label: Test Email Recipients
|
||||
text: Provide email address that will receive test sends.
|
||||
|
@ -1272,8 +1284,8 @@ ui:
|
|||
label: Enable authentication
|
||||
title: SMTP Authentication
|
||||
msg: SMTP authentication cannot be empty.
|
||||
"yes": "Yes"
|
||||
"no": "No"
|
||||
"yes": "はい"
|
||||
"no": "いいえ"
|
||||
branding:
|
||||
page_title: Branding
|
||||
logo:
|
||||
|
@ -1357,7 +1369,7 @@ ui:
|
|||
optional: (optional)
|
||||
empty: cannot be empty
|
||||
invalid: is invalid
|
||||
btn_submit: Save
|
||||
btn_submit: 保存
|
||||
not_found_props: "Required property {{ key }} not found."
|
||||
page_review:
|
||||
review: Review
|
||||
|
@ -1385,6 +1397,10 @@ ui:
|
|||
closed: closed
|
||||
reopened: reopened
|
||||
created: created
|
||||
pin: pinned
|
||||
unpin: unpinned
|
||||
show: listed
|
||||
hide: unlisted
|
||||
title: "History for"
|
||||
tag_title: "Timeline for"
|
||||
show_votes: "Show votes"
|
||||
|
@ -1411,4 +1427,8 @@ ui:
|
|||
discard_confirm: Are you sure you want to discard your draft?
|
||||
messages:
|
||||
post_deleted: This post has been deleted.
|
||||
|
||||
post_pin: This post has been pinned.
|
||||
post_unpin: This post has been unpinned.
|
||||
post_hide_list: This post has been hidden from list.
|
||||
post_show_list: This post has been shown to list.
|
||||
post_reopen: This post has been reopened.
|
||||
|
|
|
@ -22,6 +22,14 @@ backend:
|
|||
other: Close
|
||||
reopen:
|
||||
other: Reopen
|
||||
pin:
|
||||
other: Pin
|
||||
hide:
|
||||
other: Unlist
|
||||
unpin:
|
||||
other: Unpin
|
||||
show:
|
||||
other: List
|
||||
role:
|
||||
name:
|
||||
user:
|
||||
|
@ -759,6 +767,7 @@ ui:
|
|||
btn: Add question
|
||||
answers: answers
|
||||
question_detail:
|
||||
action: Action
|
||||
Asked: Asked
|
||||
asked: asked
|
||||
update: Modified
|
||||
|
@ -800,7 +809,10 @@ ui:
|
|||
confirm_btn: Reopen
|
||||
title: Reopen this post
|
||||
content: Are you sure you want to reopen?
|
||||
success: This post has been reopened
|
||||
pin:
|
||||
title: Pin this post
|
||||
content: Are you sure you wish to pinned globally? This post will appear at the top of all post lists.
|
||||
confirm_btn: Pin
|
||||
delete:
|
||||
title: Delete this post
|
||||
question: >-
|
||||
|
@ -808,7 +820,6 @@ ui:
|
|||
answer_accepted: >-
|
||||
<p>We do not recommend <strong>deleting accepted answer</strong> because doing so deprives future readers of this knowledge. </p> Repeated deletion of accepted answers can result in your account being blocked from answering. Are you sure you wish to delete?
|
||||
other: Are you sure you wish to delete?
|
||||
tip_question_deleted: This post has been deleted
|
||||
tip_answer_deleted: This answer has been deleted
|
||||
btns:
|
||||
confirm: Confirm
|
||||
|
@ -824,6 +835,7 @@ ui:
|
|||
reject: Reject
|
||||
skip: Skip
|
||||
discard_draft: Discard draft
|
||||
pinned: Pinned
|
||||
search:
|
||||
title: Search Results
|
||||
keywords: Keywords
|
||||
|
@ -1385,6 +1397,10 @@ ui:
|
|||
closed: closed
|
||||
reopened: reopened
|
||||
created: created
|
||||
pin: pinned
|
||||
unpin: unpinned
|
||||
show: listed
|
||||
hide: unlisted
|
||||
title: "History for"
|
||||
tag_title: "Timeline for"
|
||||
show_votes: "Show votes"
|
||||
|
@ -1411,4 +1427,8 @@ ui:
|
|||
discard_confirm: Are you sure you want to discard your draft?
|
||||
messages:
|
||||
post_deleted: This post has been deleted.
|
||||
|
||||
post_pin: This post has been pinned.
|
||||
post_unpin: This post has been unpinned.
|
||||
post_hide_list: This post has been hidden from list.
|
||||
post_show_list: This post has been shown to list.
|
||||
post_reopen: This post has been reopened.
|
||||
|
|
|
@ -22,6 +22,14 @@ backend:
|
|||
other: Close
|
||||
reopen:
|
||||
other: Reopen
|
||||
pin:
|
||||
other: Pin
|
||||
hide:
|
||||
other: Unlist
|
||||
unpin:
|
||||
other: Unpin
|
||||
show:
|
||||
other: List
|
||||
role:
|
||||
name:
|
||||
user:
|
||||
|
@ -759,6 +767,7 @@ ui:
|
|||
btn: Add question
|
||||
answers: answers
|
||||
question_detail:
|
||||
action: Action
|
||||
Asked: Asked
|
||||
asked: asked
|
||||
update: Modified
|
||||
|
@ -800,7 +809,10 @@ ui:
|
|||
confirm_btn: Reopen
|
||||
title: Reopen this post
|
||||
content: Are you sure you want to reopen?
|
||||
success: This post has been reopened
|
||||
pin:
|
||||
title: Pin this post
|
||||
content: Are you sure you wish to pinned globally? This post will appear at the top of all post lists.
|
||||
confirm_btn: Pin
|
||||
delete:
|
||||
title: Delete this post
|
||||
question: >-
|
||||
|
@ -808,7 +820,6 @@ ui:
|
|||
answer_accepted: >-
|
||||
<p>We do not recommend <strong>deleting accepted answer</strong> because doing so deprives future readers of this knowledge. </p> Repeated deletion of accepted answers can result in your account being blocked from answering. Are you sure you wish to delete?
|
||||
other: Are you sure you wish to delete?
|
||||
tip_question_deleted: This post has been deleted
|
||||
tip_answer_deleted: This answer has been deleted
|
||||
btns:
|
||||
confirm: Confirm
|
||||
|
@ -824,6 +835,7 @@ ui:
|
|||
reject: Reject
|
||||
skip: Skip
|
||||
discard_draft: Discard draft
|
||||
pinned: Pinned
|
||||
search:
|
||||
title: Search Results
|
||||
keywords: Keywords
|
||||
|
@ -1385,6 +1397,10 @@ ui:
|
|||
closed: closed
|
||||
reopened: reopened
|
||||
created: created
|
||||
pin: pinned
|
||||
unpin: unpinned
|
||||
show: listed
|
||||
hide: unlisted
|
||||
title: "History for"
|
||||
tag_title: "Timeline for"
|
||||
show_votes: "Show votes"
|
||||
|
@ -1411,4 +1427,8 @@ ui:
|
|||
discard_confirm: Are you sure you want to discard your draft?
|
||||
messages:
|
||||
post_deleted: This post has been deleted.
|
||||
|
||||
post_pin: This post has been pinned.
|
||||
post_unpin: This post has been unpinned.
|
||||
post_hide_list: This post has been hidden from list.
|
||||
post_show_list: This post has been shown to list.
|
||||
post_reopen: This post has been reopened.
|
||||
|
|
|
@ -22,6 +22,14 @@ backend:
|
|||
other: Close
|
||||
reopen:
|
||||
other: Reopen
|
||||
pin:
|
||||
other: Pin
|
||||
hide:
|
||||
other: Unlist
|
||||
unpin:
|
||||
other: Unpin
|
||||
show:
|
||||
other: List
|
||||
role:
|
||||
name:
|
||||
user:
|
||||
|
@ -759,6 +767,7 @@ ui:
|
|||
btn: Add question
|
||||
answers: answers
|
||||
question_detail:
|
||||
action: Action
|
||||
Asked: Asked
|
||||
asked: asked
|
||||
update: Modified
|
||||
|
@ -800,7 +809,10 @@ ui:
|
|||
confirm_btn: Reopen
|
||||
title: Reopen this post
|
||||
content: Are you sure you want to reopen?
|
||||
success: This post has been reopened
|
||||
pin:
|
||||
title: Pin this post
|
||||
content: Are you sure you wish to pinned globally? This post will appear at the top of all post lists.
|
||||
confirm_btn: Pin
|
||||
delete:
|
||||
title: Delete this post
|
||||
question: >-
|
||||
|
@ -808,7 +820,6 @@ ui:
|
|||
answer_accepted: >-
|
||||
<p>We do not recommend <strong>deleting accepted answer</strong> because doing so deprives future readers of this knowledge. </p> Repeated deletion of accepted answers can result in your account being blocked from answering. Are you sure you wish to delete?
|
||||
other: Are you sure you wish to delete?
|
||||
tip_question_deleted: This post has been deleted
|
||||
tip_answer_deleted: This answer has been deleted
|
||||
btns:
|
||||
confirm: Confirm
|
||||
|
@ -824,6 +835,7 @@ ui:
|
|||
reject: Reject
|
||||
skip: Skip
|
||||
discard_draft: Discard draft
|
||||
pinned: Pinned
|
||||
search:
|
||||
title: Search Results
|
||||
keywords: Keywords
|
||||
|
@ -1385,6 +1397,10 @@ ui:
|
|||
closed: closed
|
||||
reopened: reopened
|
||||
created: created
|
||||
pin: pinned
|
||||
unpin: unpinned
|
||||
show: listed
|
||||
hide: unlisted
|
||||
title: "History for"
|
||||
tag_title: "Timeline for"
|
||||
show_votes: "Show votes"
|
||||
|
@ -1411,4 +1427,8 @@ ui:
|
|||
discard_confirm: Are you sure you want to discard your draft?
|
||||
messages:
|
||||
post_deleted: This post has been deleted.
|
||||
|
||||
post_pin: This post has been pinned.
|
||||
post_unpin: This post has been unpinned.
|
||||
post_hide_list: This post has been hidden from list.
|
||||
post_show_list: This post has been shown to list.
|
||||
post_reopen: This post has been reopened.
|
||||
|
|
152
i18n/sk_SK.yaml
152
i18n/sk_SK.yaml
|
@ -22,6 +22,14 @@ backend:
|
|||
other: Close
|
||||
reopen:
|
||||
other: Reopen
|
||||
pin:
|
||||
other: Pin
|
||||
hide:
|
||||
other: Unlist
|
||||
unpin:
|
||||
other: Unpin
|
||||
show:
|
||||
other: List
|
||||
role:
|
||||
name:
|
||||
user:
|
||||
|
@ -34,9 +42,9 @@ backend:
|
|||
user:
|
||||
other: Predvolené bez špeciálneho prístupu.
|
||||
admin:
|
||||
other: Má plnú moc a prístup k stránke.
|
||||
other: Má plnú moc a prístup ku stránke.
|
||||
moderator:
|
||||
other: Má prístup ku všetkým príspevkom okrem nastavení správcu.
|
||||
other: Má prístup ku všetkým príspevkom okrem nastavenia správcu.
|
||||
email:
|
||||
other: E-mail
|
||||
password:
|
||||
|
@ -46,7 +54,7 @@ backend:
|
|||
error:
|
||||
admin:
|
||||
cannot_update_their_password:
|
||||
other: Svoje heslo nemôžete upraviť.
|
||||
other: Svoje heslo upraviť.
|
||||
cannot_modify_self_status:
|
||||
other: Nemôžete upraviť svoj stav.
|
||||
email_or_password_wrong:
|
||||
|
@ -116,10 +124,10 @@ backend:
|
|||
handle_failed:
|
||||
other: Spracovanie prehľadu zlyhalo.
|
||||
not_found:
|
||||
other: Prehľad sa nenašiel.
|
||||
other: Hlásenie sa nenašlo.
|
||||
tag:
|
||||
already_exist:
|
||||
other: Tento tag už existuje.
|
||||
other: Značka už existuje.
|
||||
not_found:
|
||||
other: Značka sa nenašla.
|
||||
recommend_tag_not_found:
|
||||
|
@ -131,7 +139,7 @@ backend:
|
|||
cannot_update:
|
||||
other: Žiadne povolenie na aktualizáciu.
|
||||
is_used_cannot_delete:
|
||||
other: Nemôžete odstrániť značku, ktorá sa používa
|
||||
other: Zadajte e-mailovú adresu, na ktorú sa budú odosielať testy
|
||||
cannot_set_synonym_as_itself:
|
||||
other: Synonymum aktuálnej značky nemôžete nastaviť ako samotnú.
|
||||
smtp:
|
||||
|
@ -268,7 +276,7 @@ ui:
|
|||
how_to_format:
|
||||
title: Ako formátovať
|
||||
desc: >-
|
||||
<ul class="mb-0"><li><p class="mb-2">Vytvorenie odkazov</p><pre class="mb-2"><code><https://url.com><br/><br/>[Title](https://url.com)</code></pre></li><li><p class="mb-2">vložiť návratky medzi odseky</p></li><li><p class="mb-2"><em>_italic_</em> or **<strong>bold</strong>**</p></li><li><p class="mb-2">odsadenie kódu o 4 medzery</p></li><li><p class="mb-2">citovať podľa umiestnenie <code>></code> na začiatok riadku</p></li><li><p class="mb-2">backtick označenie <code>`ako _this_`</code></p></li><li><p class="mb-2">vytvorte kódové ploty s <code>`</code></p><pre class="mb-0"><code>```<br/>code here<br/>```</code></pre></li></ul>
|
||||
<ul class="mb-0"><li><p class="mb-2">na vytváranie odkazov</p><pre class="mb-2"><code><https://url.com><br/><br/>[Title](https://url.com)</code></pre></li><li><p class="mb-2">vracať medzi odsekmi</p></li><li><p class="mb-2"><em>_italic_</em> alebo **<strong>tučné</strong>**</p></li><li><p class="mb-2">odsadenie kódu o 4 medzery</p></li><li><p class="mb-2">citát umiestnením <code>></code> na začiatku riadku</p></li><li><p class="mb-2">backtick uniká <code>`ako _this_`</code></p></li><li><p class="mb-2">vytvorte kódové ploty s <code>`</code></p><pre class="mb-0"><code>```<br/>kódom tu<br/>```</code></pre></li></ul>
|
||||
pagination:
|
||||
prev: Predch
|
||||
next: Ďalšie
|
||||
|
@ -295,7 +303,7 @@ ui:
|
|||
account_suspended: Účet pozastavený
|
||||
admin: Administrátor
|
||||
change_email: Upraviť e-mail
|
||||
install: Answer Inštalácia
|
||||
install: Odpoveď Inštalácia
|
||||
upgrade: Answer Upgrade
|
||||
maintenance: Údržba webových stránok
|
||||
users: Užívatelia
|
||||
|
@ -386,7 +394,7 @@ ui:
|
|||
label: Description
|
||||
btn_cancel: Zrušiť
|
||||
btn_confirm: Pridať
|
||||
uploading: Nahrávanie
|
||||
uploading: Nahráva sa
|
||||
indent:
|
||||
text: Indent
|
||||
outdent:
|
||||
|
@ -399,7 +407,7 @@ ui:
|
|||
form:
|
||||
fields:
|
||||
url:
|
||||
label: URL adresa
|
||||
label: URL
|
||||
msg:
|
||||
empty: URL adresa nemôže byť prázdna.
|
||||
name:
|
||||
|
@ -423,7 +431,7 @@ ui:
|
|||
msg:
|
||||
empty: Vyberte dôvod.
|
||||
report_modal:
|
||||
flag_title: Označujem, aby som tento príspevok nahlásil ako ...
|
||||
flag_title: Nahlasujem nahlásenie tohto príspevku ako...
|
||||
close_title: Tento príspevok zatváram ako ...
|
||||
review_question_title: Kontrola otázky
|
||||
review_answer_title: Kontrola odpovede
|
||||
|
@ -445,7 +453,7 @@ ui:
|
|||
range: Zobrazovaný názov do 35 znakov.
|
||||
slug_name:
|
||||
label: URL Slug
|
||||
desc: 'Musíte použiť znakovú sadu "a-z", "0-9", "+ # - ."'
|
||||
desc: URL slug do 35 znakov.
|
||||
msg:
|
||||
empty: URL slug nemôže byť prázdny.
|
||||
range: URL slug do 35 znakov.
|
||||
|
@ -546,7 +554,7 @@ ui:
|
|||
button_follow: Sledovať
|
||||
button_following: Sledované
|
||||
tag_label: otázky
|
||||
search_placeholder: Filter podľa názvu značky
|
||||
search_placeholder: Filtrujte podľa názvu značky
|
||||
no_desc: Značka nemá popis.
|
||||
more: Viac
|
||||
ask:
|
||||
|
@ -560,10 +568,10 @@ ui:
|
|||
label: Revízia
|
||||
title:
|
||||
label: Názov
|
||||
placeholder: Buďte konkrétny a predstavte si, že kladiete otázku inej osobe
|
||||
placeholder: Buďte konkrétni a predstavte si, že kladiete otázku inej osobe
|
||||
msg:
|
||||
empty: Názov nemôže byť prázdny.
|
||||
range: Názov má viac ako 150 znakov --
|
||||
range: Názov do 150 znakov
|
||||
body:
|
||||
label: Telo
|
||||
msg:
|
||||
|
@ -571,7 +579,7 @@ ui:
|
|||
tags:
|
||||
label: Značky --
|
||||
msg:
|
||||
empty: Značky nemôžu byť prázdne.
|
||||
empty: Štítky nemôžu byť prázdne.
|
||||
answer:
|
||||
label: Odpoveď
|
||||
msg:
|
||||
|
@ -580,17 +588,17 @@ ui:
|
|||
label: Upraviť zhrnutie
|
||||
placeholder: >-
|
||||
Stručne vysvetlite svoje zmeny (opravený pravopis, opravená gramatika, vylepšené formátovanie)
|
||||
btn_post_question: Pošlite svoju otázku
|
||||
btn_post_question: Uverejnite svoju otázku
|
||||
btn_save_edits: Uložiť úpravy
|
||||
answer_question: Odpovedzte na svoju vlastnú otázku
|
||||
post_question&answer: Pošlite svoju otázku a odpoveď
|
||||
post_question&answer: Uverejnite svoju otázku a odpoveď
|
||||
tag_selector:
|
||||
add_btn: Pridať značku
|
||||
create_btn: Vytvoriť novú značku
|
||||
search_tag: Vyhľadať značku --
|
||||
hint: "„Popíšte, o čom je vaša otázka, vyžaduje sa aspoň jedna značka.“"
|
||||
no_result: Žiadne značky sa neyhodujú
|
||||
tag_required_text: Požadovaná značka (aspoň jedna)
|
||||
hint: "Popíšte, čoho sa vaša otázka týka, vyžaduje sa aspoň jedna značka."
|
||||
no_result: Nezodpovedajú žiadne značky
|
||||
tag_required_text: Povinný štítok (aspoň jeden)
|
||||
header:
|
||||
nav:
|
||||
question: Otázky
|
||||
|
@ -605,7 +613,7 @@ ui:
|
|||
placeholder: Vyhľadávanie
|
||||
footer:
|
||||
build_on: >-
|
||||
Postavený na <1> Answer </1>- open-source software, ktorý poháňa Q&A komunity.<br />Vyrobené s láskou © {{cc}}.
|
||||
Postavené na <1> Answer </1> – softvéri s otvoreným zdrojom, ktorý poháňa komunity otázok a odpovedí.<br />Vyrobené s láskou © {{cc}}.
|
||||
upload_img:
|
||||
name: Zmena
|
||||
loading: načítavanie...
|
||||
|
@ -696,17 +704,17 @@ ui:
|
|||
msg_range: Zobrazované meno nesmie mať viac ako 30 znakov.
|
||||
username:
|
||||
label: Užívateľské meno
|
||||
caption: Ľudia vás môžu spomenúť ako "@username".
|
||||
caption: Ľudia vás môžu spomenúť ako „@používateľské meno“.
|
||||
msg: Užívateľské meno nemôže byť prázdne.
|
||||
msg_range: Používateľské meno nesmie mať viac ako 30 znakov.
|
||||
character: 'Musí použiť súpravu znakov "a-z", "0-9", " - . _"'
|
||||
character: 'Musíte použiť znakovú sadu "a-z", "0-9", "- . _"'
|
||||
avatar:
|
||||
label: Profilová fotka
|
||||
gravatar: Gravatar
|
||||
gravatar_text: Fotku môžete zmeniť na <1>gravatar.com</1>
|
||||
gravatar_text: Obrázok môžete zmeniť na <1>gravatar.com</1>
|
||||
custom: Vlastný
|
||||
btn_refresh: Obnoviť
|
||||
custom_text: Môžete nahrať svoju fotku.
|
||||
custom_text: Môžete nahrať svoj obrázok.
|
||||
default: Systém
|
||||
msg: Nahrajte avatara prosím
|
||||
bio:
|
||||
|
@ -728,17 +736,17 @@ ui:
|
|||
change_email_btn: Zmeniť e-mail
|
||||
change_pass_btn: Zmeniť heslo
|
||||
change_email_info: >-
|
||||
Na túto adresu sme poslali e-mail. Postupujte podľa inštrukcií.
|
||||
Na túto adresu sme poslali e-mail. Postupujte podľa pokynov na potvrdenie.
|
||||
email:
|
||||
label: E-mail
|
||||
msg: E-mail nemôže byť prázdny.
|
||||
password_title: Heslo
|
||||
current_pass:
|
||||
label: Súčasné heslo
|
||||
label: Aktuálne heslo
|
||||
msg:
|
||||
empty: Aktuálne heslo nemôže byť prázdne.
|
||||
length: Dĺžka musí byť medzi 8 a 32.
|
||||
different: Zadané heslá sa nezhodujú.
|
||||
different: Dve zadané heslá sa nezhodujú.
|
||||
new_pass:
|
||||
label: Nové heslo
|
||||
pass_confirm:
|
||||
|
@ -751,14 +759,15 @@ ui:
|
|||
toast:
|
||||
update: aktualizácia úspešna
|
||||
update_password: Heslo bolo úspešne zmenené.
|
||||
flag_success: Ďakujeme za označenie.
|
||||
forbidden_operate_self: Zakázané pracovať na sebe
|
||||
flag_success: Ďakujeme za nahlásenie.
|
||||
forbidden_operate_self: Zakázané operovať seba
|
||||
review: Vaša revízia sa zobrazí po preskúmaní.
|
||||
related_question:
|
||||
title: Súvisiace otázky
|
||||
btn: Pridať otázku
|
||||
answers: odpovede
|
||||
question_detail:
|
||||
action: Action
|
||||
Asked: Opýtané
|
||||
asked: opýtané
|
||||
update: Aktualizované
|
||||
|
@ -800,7 +809,10 @@ ui:
|
|||
confirm_btn: Reopen
|
||||
title: Znovu otvoriť tento príspevok
|
||||
content: Ste si istý, že ho chcete znovu otvoriť?
|
||||
success: Tento príspevok bol znovu otvorený
|
||||
pin:
|
||||
title: Pin this post
|
||||
content: Are you sure you wish to pinned globally? This post will appear at the top of all post lists.
|
||||
confirm_btn: Pin
|
||||
delete:
|
||||
title: Odstrániť tento príspevok
|
||||
question: >-
|
||||
|
@ -808,7 +820,6 @@ ui:
|
|||
answer_accepted: >-
|
||||
<p>Neodporúčame <strong>odstránenie akceptovanej odpovede</strong> pretože týmto oberáte budúcich čitateľov o tieto vedomostí.</p>Opakované mazanie akceptovaných odpovedí môže mať za následok zablokovanie možnosti odpovedať z vášho účtu. Ste si istí, že chcete odstrániť odpoveď?
|
||||
other: Ste si istí, že ju chcete odstrániť?
|
||||
tip_question_deleted: Tento príspevok bol odstránený
|
||||
tip_answer_deleted: Táto odpoveď bola odstránená
|
||||
btns:
|
||||
confirm: Potvrdiť
|
||||
|
@ -824,6 +835,7 @@ ui:
|
|||
reject: Odmietnuť
|
||||
skip: Preskočiť
|
||||
discard_draft: Zahodiť koncept
|
||||
pinned: Pinned
|
||||
search:
|
||||
title: Výsledky vyhľadávania
|
||||
keywords: Kľúčové slová
|
||||
|
@ -840,7 +852,7 @@ ui:
|
|||
more: Viac
|
||||
tips:
|
||||
title: Tipy na pokročilé vyhľadávanie
|
||||
tag: "<1>[tag]</1> vyhľadávať v rámci značky"
|
||||
tag: "<1>[tag]</1> hľadať v rámci značky"
|
||||
user: "<1>user:username</1> hľadať podľa autora"
|
||||
answer: "<1>answers:0</1> nezodpovedané otázky"
|
||||
score: "<1>score:3</1> Príspevky so skóre 3+"
|
||||
|
@ -891,7 +903,7 @@ ui:
|
|||
answered: zodpovedané
|
||||
asked: opýtané
|
||||
closed: uzatvorené
|
||||
follow_a_tag: Sledovať značku
|
||||
follow_a_tag: Postupujte podľa značky
|
||||
more: Viac
|
||||
personal:
|
||||
overview: Prehľad
|
||||
|
@ -915,7 +927,7 @@ ui:
|
|||
top_answers: Najlepšie odpovede
|
||||
top_questions: Najlepšie otázky
|
||||
stats: Štatistiky
|
||||
list_empty: Nenašli sa žiadne príspevky.<br/>Chceli by ste vybrať inú kartu?
|
||||
list_empty: Nenašli sa žiadne príspevky.<br />Možno by ste chceli vybrať inú kartu?
|
||||
accepted: Prijaté
|
||||
answered: zodpovedané
|
||||
asked: opýtané
|
||||
|
@ -924,13 +936,13 @@ ui:
|
|||
mod_short: mod
|
||||
mod_long: Moderátori
|
||||
x_reputation: reputácia
|
||||
x_votes: Získané hlasy
|
||||
x_votes: prijatých hlasov
|
||||
x_answers: odpovede
|
||||
x_questions: otázky
|
||||
install:
|
||||
title: Odpoveď
|
||||
next: Ďalšie
|
||||
done: hotovo
|
||||
done: Hotový
|
||||
config_yaml_error: Nie je možné vytvoriť súbor config.yaml.
|
||||
lang:
|
||||
label: Vyberte jazyk
|
||||
|
@ -953,14 +965,14 @@ ui:
|
|||
placeholder: odpoveď
|
||||
msg: Názov databázy nemôže byť prázdny.
|
||||
db_file:
|
||||
label: Databázový súbor
|
||||
label: Súbor databázy
|
||||
placeholder: /data/answer.db
|
||||
msg: Databázový súbor nemôže byť prázdny.
|
||||
config_yaml:
|
||||
title: Vytvoriť config.yaml
|
||||
label: Vytvorený súbor Config.yaml.
|
||||
desc: >-
|
||||
Môžete vytvoriť <1>config.yaml</1> súbor manuálne v <1>/var/wwww/xxx/</1> adresári a vložte do neho nasledujúci text.
|
||||
Súbor <1>config.yaml</1> môžete vytvoriť manuálne v adresári <1>/var/www/xxx/</1> a vložiť doň nasledujúci text.
|
||||
info: Potom, čo ste to urobili, kliknite na tlačidlo „Ďalej“.
|
||||
site_information: Informácie o stránke
|
||||
admin_account: Správca
|
||||
|
@ -999,16 +1011,16 @@ ui:
|
|||
good_luck: "„Bavte sa a veľa šťastia!“"
|
||||
warn_title: Upozornenie
|
||||
warn_desc: >-
|
||||
Súbor <1>config.yaml</1> už existuje. Ak v tomto súbore potrebujete resetovať niektorú z konfiguračných položiek, najskôr ho odstráňte.
|
||||
Súbor <1>config.yaml</1> už existuje. Ak potrebujete resetovať niektorú z konfiguračných položiek v tomto súbore, najskôr ju odstráňte.
|
||||
install_now: Môžete skúsiť <1>installing now</1>.
|
||||
installed: Už nainštalované
|
||||
installed_desc: >-
|
||||
Zdá sa, že ste už aplikáciu answer nainštalovali. Ak chcete aplikáciu preinštalovať, najprv vymažte staré tabuľky z databázy.
|
||||
db_failed: Databázové pripojenie zlyhalo
|
||||
db_failed_desc: >-
|
||||
To buď znamená, že informácia o databáze v súbore <1>config.yaml</1> sú nesprávna alebo že sa nepodarilo nadviazať kontakt s databázovým serverom. To môže znamenať, že databázový server vášho hostiteľa nefunguje.
|
||||
To buď znamená, že informácie o databáze vo vašom súbore <1>config.yaml</1> sú nesprávne, alebo že sa nepodarilo nadviazať kontakt s databázovým serverom. Môže to znamenať, že databázový server vášho hostiteľa nefunguje.
|
||||
counts:
|
||||
views: videnia
|
||||
views: názory
|
||||
votes: hlasy
|
||||
answers: odpovede
|
||||
accepted: prijaté
|
||||
|
@ -1106,7 +1118,7 @@ ui:
|
|||
status_modal:
|
||||
title: "Zmena {{ type }} stav na..."
|
||||
normal_name: normálne
|
||||
normal_desc: Normálny príspevok je k dispozícii pre všetkých.
|
||||
normal_desc: Normálny príspevok dostupný pre každého.
|
||||
closed_name: Uzavreté
|
||||
closed_desc: "Na uzavretú otázku nemôžete odpovedať, ale stále ju môžete upravovať, hlasovať a komentovať."
|
||||
deleted_name: Vymazané
|
||||
|
@ -1202,20 +1214,20 @@ ui:
|
|||
name:
|
||||
label: Názov stránky
|
||||
msg: Názov stránky nemôže byť prázdny.
|
||||
text: "Názov tejto stránky, ako sa používa v titulnej značke."
|
||||
text: "Názov tejto lokality, ako sa používa v značke názvu."
|
||||
site_url:
|
||||
label: URL stránky
|
||||
msg: URL stránky nemôže byť prázdne.
|
||||
validate: Prosím vložte platnú URL.
|
||||
msg: Adresa Url stránky nemôže byť prázdna.
|
||||
validate: Prosím uveďte platnú webovú adresu.
|
||||
text: Adresa vašej stránky.
|
||||
short_desc:
|
||||
label: Krátky popis stránky
|
||||
msg: Krátky popis stránky nemôže byť prázdny.
|
||||
text: "Krátky popis, ako sa používa v titulnej značke na domovskej stránke."
|
||||
text: "Krátky popis, ako sa používa v značke názvu na domovskej stránke."
|
||||
desc:
|
||||
label: Popis stránky
|
||||
msg: Popis stránky nemôže byť prázdny.
|
||||
text: "Popíšte túto stránku jednou vetou, ako sa používa v značke meta description."
|
||||
text: "Opíšte túto stránku jednou vetou, ako sa používa v značke meta description."
|
||||
contact_email:
|
||||
label: Kontaktný e-mail
|
||||
msg: Kontaktný e-mail nemôže byť prázdny.
|
||||
|
@ -1226,11 +1238,11 @@ ui:
|
|||
language:
|
||||
label: Jazyk rozhrania
|
||||
msg: Jazyk rozhrania nemôže byť prázdny.
|
||||
text: Jazyk používateľského rozhrania. Zmení sa po obnovení stránky.
|
||||
text: Jazyk používateľského rozhrania. Zmení sa, keď stránku obnovíte.
|
||||
time_zone:
|
||||
label: Časové pásmo
|
||||
msg: Časové pásmo nemôže byť prázdne.
|
||||
text: Vyberte si mesto v rovnakom časovom pásme v akom ste vy.
|
||||
text: Vyberte si mesto v rovnakom časovom pásme ako vy.
|
||||
avatar:
|
||||
label: Predvolený avatar
|
||||
text: Pre používateľov bez vlastného avatara.
|
||||
|
@ -1238,11 +1250,11 @@ ui:
|
|||
page_title: SMTP
|
||||
from_email:
|
||||
label: E-mail odosielateľa
|
||||
msg: E-mail odosielateľa nemôže byť prázdny.
|
||||
msg: Z e-mailu nemôže byť prázdne.
|
||||
text: E-mailová adresa, z ktorej sa odosielajú e-maily.
|
||||
from_name:
|
||||
label: Meno odosielateľa
|
||||
msg: Meno odosielateľa nemôže byť prázdne.
|
||||
msg: Názov od nemôže byť prázdny.
|
||||
text: Meno, z ktorého sa odosielajú e-maily.
|
||||
smtp_host:
|
||||
label: Hostiteľ SMTP
|
||||
|
@ -1256,22 +1268,22 @@ ui:
|
|||
none: Žiadne
|
||||
smtp_port:
|
||||
label: SMTP Port
|
||||
msg: Port SMTP musí byť číslo z intervalu 1 ~ 65535.
|
||||
text: Port na váš e-mailový server.
|
||||
msg: Port SMTP musí byť číslo 1 ~ 65535.
|
||||
text: Port na váš poštový server.
|
||||
smtp_username:
|
||||
label: Používateľské meno SMTP
|
||||
label: Uživatelské Meno SMTP
|
||||
msg: Používateľské meno SMTP nemôže byť prázdne.
|
||||
smtp_password:
|
||||
label: Heslo SMTP
|
||||
msg: Heslo SMTP nemôže byť prázdne.
|
||||
test_email_recipient:
|
||||
label: Prijemcovia testovacieho e-mailu
|
||||
label: Testovať príjemcov e-mailov
|
||||
text: Zadajte e-mailovú adresu, na ktorú sa budú odosielať testy.
|
||||
msg: Príjemcovia testovacieho e-mailu sú neplatní
|
||||
smtp_authentication:
|
||||
label: Povoliť autentifikáciu
|
||||
title: Autentifikácia SMTP
|
||||
msg: Autentifikácia SMTP nemôže byť prázdna.
|
||||
title: Overenie SMTP
|
||||
msg: Overenie SMTP nemôže byť prázdne.
|
||||
"yes": "Áno"
|
||||
"no": "Nie"
|
||||
branding:
|
||||
|
@ -1279,7 +1291,7 @@ ui:
|
|||
logo:
|
||||
label: Logo
|
||||
msg: Logo nemôže byť prázdne.
|
||||
text: Obrázok loga v ľavej hornej časti vašej stránky. Použite široký obdĺžnikový obrázok s výškou 56 a pomerom strán väčším ako 3:1. Ak nezadáte nič, tak sa zobrazí text názvu stránky.
|
||||
text: Obrázok loga v ľavej hornej časti vašej stránky. Použite široký obdĺžnikový obrázok s výškou 56 a pomerom strán väčším ako 3:1. Ak ho ponecháte prázdne, zobrazí sa text názvu stránky.
|
||||
mobile_logo:
|
||||
label: Logo mobilu
|
||||
text: Logo použité na mobilnej verzii vášho webu. Použite široký obdĺžnikový obrázok s výškou 56. Ak pole ponecháte prázdne, použije sa obrázok z nastavenia „logo“.
|
||||
|
@ -1289,7 +1301,7 @@ ui:
|
|||
text: Obrázok použitý ako základ pre ikony metadát. V ideálnom prípade by mal byť väčšií ako 512 x 512.
|
||||
favicon:
|
||||
label: favicon
|
||||
text: Favicon pre váš web. Ak chcete cez CDN fungovať správne, musí byť vo formáte png. Veľkosť sa zmení na 32 x 32. Ak nebude nič zadané, použije sa „štvorcová ikona“.
|
||||
text: Favicon pre váš web. Ak chcete správne fungovať cez CDN, musí to byť png. Veľkosť sa zmení na 32 x 32. Ak zostane prázdne, použije sa „štvorcová ikona“.
|
||||
legal:
|
||||
page_title: Legálne
|
||||
terms_of_service:
|
||||
|
@ -1301,15 +1313,15 @@ ui:
|
|||
write:
|
||||
page_title: Písať
|
||||
recommend_tags:
|
||||
label: Odporúčané značky
|
||||
label: Odporučiť značky
|
||||
text: "Vyššie zadajte tag slug, jednu značku na riadok."
|
||||
required_tag:
|
||||
title: Požadovaná značka
|
||||
title: Povinný štítok
|
||||
label: Nastavte odporúčanú značku podľa potreby
|
||||
text: "Každá nová otázka musí mať aspoň jedenu odporúčaciu značku."
|
||||
reserved_tags:
|
||||
label: Vyhradené značky
|
||||
text: "Vyhradené značky môže k príspevku pridať iba moderátor"
|
||||
text: "Vyhradené štítky môže k príspevku pridať iba moderátor."
|
||||
seo:
|
||||
page_title: SEO
|
||||
permalink:
|
||||
|
@ -1385,12 +1397,16 @@ ui:
|
|||
closed: uzavreté
|
||||
reopened: znovu otvorené
|
||||
created: vytvorené
|
||||
pin: pinned
|
||||
unpin: unpinned
|
||||
show: listed
|
||||
hide: unlisted
|
||||
title: "História pre"
|
||||
tag_title: "Časová os pre"
|
||||
show_votes: "Zobraziť hlasy"
|
||||
n_or_a: N/A
|
||||
title_for_question: "Časová os pre"
|
||||
title_for_answer: "Časová os odpovede pre {{ title }} od {{ author }}"
|
||||
title_for_answer: "Časová os odpovede na {{ title }} od {{ author }}"
|
||||
title_for_tag: "Časová os pre značku"
|
||||
datetime: Dátum a čas
|
||||
type: Typ
|
||||
|
@ -1411,4 +1427,8 @@ ui:
|
|||
discard_confirm: Naozaj chcete zahodiť svoj koncept?
|
||||
messages:
|
||||
post_deleted: Tento príspevok bol odstránený.
|
||||
|
||||
post_pin: This post has been pinned.
|
||||
post_unpin: This post has been unpinned.
|
||||
post_hide_list: This post has been hidden from list.
|
||||
post_show_list: This post has been shown to list.
|
||||
post_reopen: This post has been reopened.
|
||||
|
|
|
@ -22,6 +22,14 @@ backend:
|
|||
other: Close
|
||||
reopen:
|
||||
other: Reopen
|
||||
pin:
|
||||
other: Pin
|
||||
hide:
|
||||
other: Unlist
|
||||
unpin:
|
||||
other: Unpin
|
||||
show:
|
||||
other: List
|
||||
role:
|
||||
name:
|
||||
user:
|
||||
|
@ -759,6 +767,7 @@ ui:
|
|||
btn: Add question
|
||||
answers: answers
|
||||
question_detail:
|
||||
action: Action
|
||||
Asked: Asked
|
||||
asked: asked
|
||||
update: Modified
|
||||
|
@ -800,7 +809,10 @@ ui:
|
|||
confirm_btn: Reopen
|
||||
title: Reopen this post
|
||||
content: Are you sure you want to reopen?
|
||||
success: This post has been reopened
|
||||
pin:
|
||||
title: Pin this post
|
||||
content: Are you sure you wish to pinned globally? This post will appear at the top of all post lists.
|
||||
confirm_btn: Pin
|
||||
delete:
|
||||
title: Delete this post
|
||||
question: >-
|
||||
|
@ -808,7 +820,6 @@ ui:
|
|||
answer_accepted: >-
|
||||
<p>We do not recommend <strong>deleting accepted answer</strong> because doing so deprives future readers of this knowledge. </p> Repeated deletion of accepted answers can result in your account being blocked from answering. Are you sure you wish to delete?
|
||||
other: Are you sure you wish to delete?
|
||||
tip_question_deleted: This post has been deleted
|
||||
tip_answer_deleted: This answer has been deleted
|
||||
btns:
|
||||
confirm: Confirm
|
||||
|
@ -824,6 +835,7 @@ ui:
|
|||
reject: Reject
|
||||
skip: Skip
|
||||
discard_draft: Discard draft
|
||||
pinned: Pinned
|
||||
search:
|
||||
title: Search Results
|
||||
keywords: Keywords
|
||||
|
@ -1385,6 +1397,10 @@ ui:
|
|||
closed: closed
|
||||
reopened: reopened
|
||||
created: created
|
||||
pin: pinned
|
||||
unpin: unpinned
|
||||
show: listed
|
||||
hide: unlisted
|
||||
title: "History for"
|
||||
tag_title: "Timeline for"
|
||||
show_votes: "Show votes"
|
||||
|
@ -1411,4 +1427,8 @@ ui:
|
|||
discard_confirm: Are you sure you want to discard your draft?
|
||||
messages:
|
||||
post_deleted: This post has been deleted.
|
||||
|
||||
post_pin: This post has been pinned.
|
||||
post_unpin: This post has been unpinned.
|
||||
post_hide_list: This post has been hidden from list.
|
||||
post_show_list: This post has been shown to list.
|
||||
post_reopen: This post has been reopened.
|
||||
|
|
|
@ -22,6 +22,14 @@ backend:
|
|||
other: Close
|
||||
reopen:
|
||||
other: Reopen
|
||||
pin:
|
||||
other: Pin
|
||||
hide:
|
||||
other: Unlist
|
||||
unpin:
|
||||
other: Unpin
|
||||
show:
|
||||
other: List
|
||||
role:
|
||||
name:
|
||||
user:
|
||||
|
@ -759,6 +767,7 @@ ui:
|
|||
btn: Add question
|
||||
answers: answers
|
||||
question_detail:
|
||||
action: Action
|
||||
Asked: Asked
|
||||
asked: asked
|
||||
update: Modified
|
||||
|
@ -800,7 +809,10 @@ ui:
|
|||
confirm_btn: Reopen
|
||||
title: Reopen this post
|
||||
content: Are you sure you want to reopen?
|
||||
success: This post has been reopened
|
||||
pin:
|
||||
title: Pin this post
|
||||
content: Are you sure you wish to pinned globally? This post will appear at the top of all post lists.
|
||||
confirm_btn: Pin
|
||||
delete:
|
||||
title: Delete this post
|
||||
question: >-
|
||||
|
@ -808,7 +820,6 @@ ui:
|
|||
answer_accepted: >-
|
||||
<p>We do not recommend <strong>deleting accepted answer</strong> because doing so deprives future readers of this knowledge. </p> Repeated deletion of accepted answers can result in your account being blocked from answering. Are you sure you wish to delete?
|
||||
other: Are you sure you wish to delete?
|
||||
tip_question_deleted: This post has been deleted
|
||||
tip_answer_deleted: This answer has been deleted
|
||||
btns:
|
||||
confirm: Confirm
|
||||
|
@ -824,6 +835,7 @@ ui:
|
|||
reject: Reject
|
||||
skip: Skip
|
||||
discard_draft: Discard draft
|
||||
pinned: Pinned
|
||||
search:
|
||||
title: Search Results
|
||||
keywords: Keywords
|
||||
|
@ -1385,6 +1397,10 @@ ui:
|
|||
closed: closed
|
||||
reopened: reopened
|
||||
created: created
|
||||
pin: pinned
|
||||
unpin: unpinned
|
||||
show: listed
|
||||
hide: unlisted
|
||||
title: "History for"
|
||||
tag_title: "Timeline for"
|
||||
show_votes: "Show votes"
|
||||
|
@ -1411,4 +1427,8 @@ ui:
|
|||
discard_confirm: Are you sure you want to discard your draft?
|
||||
messages:
|
||||
post_deleted: This post has been deleted.
|
||||
|
||||
post_pin: This post has been pinned.
|
||||
post_unpin: This post has been unpinned.
|
||||
post_hide_list: This post has been hidden from list.
|
||||
post_show_list: This post has been shown to list.
|
||||
post_reopen: This post has been reopened.
|
||||
|
|
123
i18n/zh_CN.yaml
123
i18n/zh_CN.yaml
|
@ -22,6 +22,14 @@ backend:
|
|||
other: 关闭
|
||||
reopen:
|
||||
other: 重新打开
|
||||
pin:
|
||||
other: 置顶
|
||||
hide:
|
||||
other: 隐藏
|
||||
unpin:
|
||||
other: 取消置顶
|
||||
show:
|
||||
other: 显示
|
||||
role:
|
||||
name:
|
||||
user:
|
||||
|
@ -37,6 +45,58 @@ backend:
|
|||
other: 拥有管理网站的全部权限。
|
||||
moderator:
|
||||
other: 拥有访问除管理员设置以外的所有权限。
|
||||
privilege:
|
||||
level_1:
|
||||
description:
|
||||
other: 等级1(创业社区所需的声望最低)
|
||||
level_2:
|
||||
description:
|
||||
other: 等级2(创业社区所需的声望较低)
|
||||
level_3:
|
||||
description:
|
||||
other: 等级3(成熟社区所需的声望较高)
|
||||
rank_question_add_label:
|
||||
other: 提问
|
||||
rank_answer_add_label:
|
||||
other: 回答问题
|
||||
rank_comment_add_label:
|
||||
other: 发表评论
|
||||
rank_report_add_label:
|
||||
other: 举报
|
||||
rank_comment_vote_up_label:
|
||||
other: 评论点赞
|
||||
rank_link_url_limit_label:
|
||||
other: 一次发布超过两个链接
|
||||
rank_question_vote_up_label:
|
||||
other: 问题点赞
|
||||
rank_answer_vote_up_label:
|
||||
other: 答案点赞
|
||||
rank_question_vote_down_label:
|
||||
other: 问题点踩
|
||||
rank_answer_vote_down_label:
|
||||
other: 答案点踩
|
||||
rank_tag_add_label:
|
||||
other: 创建新标签
|
||||
rank_tag_edit_label:
|
||||
other: 编辑标签描述(需要审核)
|
||||
rank_question_edit_label:
|
||||
other: 编辑他人提问(需要审核)
|
||||
rank_answer_edit_label:
|
||||
other: 编辑他人回答(需要审核)
|
||||
rank_question_edit_without_review_label:
|
||||
other: 编辑他人提问(无需审核)
|
||||
rank_answer_edit_without_review_label:
|
||||
other: 编辑他人回答(无需审核)
|
||||
rank_question_audit_label:
|
||||
other: 审核问题编辑
|
||||
rank_answer_audit_label:
|
||||
other: 审核答案编辑
|
||||
rank_tag_audit_label:
|
||||
other: 审核标签编辑
|
||||
rank_tag_edit_without_review_label:
|
||||
other: 编辑标签描述(无需审核)
|
||||
rank_tag_synonym_label:
|
||||
other: 管理标签同义词
|
||||
email:
|
||||
other: 邮箱
|
||||
password:
|
||||
|
@ -74,6 +134,8 @@ backend:
|
|||
other: 邮箱需要验证。
|
||||
verify_url_expired:
|
||||
other: 邮箱验证的网址已过期,请重新发送邮件。
|
||||
illegal_email_domain_error:
|
||||
other: 该域名的邮箱无法使用。请尝试更换其他邮箱。
|
||||
lang:
|
||||
not_found:
|
||||
other: 语言未找到
|
||||
|
@ -163,6 +225,10 @@ backend:
|
|||
other: 您不能修改自己的角色。
|
||||
not_allowed_registration:
|
||||
other: 目前该站点未开放注册
|
||||
access_denied:
|
||||
other: 访问被拒绝
|
||||
page_access_denied:
|
||||
other: 你没有权限进入这个页面。
|
||||
config:
|
||||
read_config_failed:
|
||||
other: 读取配置失败
|
||||
|
@ -519,7 +585,7 @@ ui:
|
|||
使用评论提问更多信息或者提出改进意见。尽量避免使用评论功能回答问题。
|
||||
tip_answer: >-
|
||||
使用评论对回答者进行回复,或者通知回答者你已更新了问题的内容。如果要补充或者完善问题的内容,请在原问题中更改。
|
||||
tip_vote: It adds something useful to the post
|
||||
tip_vote: 它给帖子添加了一些有用的内容
|
||||
edit_answer:
|
||||
title: 编辑回答
|
||||
default_reason: 编辑回答
|
||||
|
@ -760,6 +826,7 @@ ui:
|
|||
btn: 我要提问
|
||||
answers: 个回答
|
||||
question_detail:
|
||||
action: 操作
|
||||
Asked: 提问于
|
||||
asked: 提问于
|
||||
update: 修改于
|
||||
|
@ -770,11 +837,11 @@ ui:
|
|||
answered: 回答于
|
||||
closed_in: 关闭于
|
||||
show_exist: 查看相关问题。
|
||||
useful: Useful
|
||||
question_useful: It is useful and clear
|
||||
question_un_useful: It is unclear or not useful
|
||||
answer_useful: It is useful
|
||||
answer_un_useful: It is not useful
|
||||
useful: 有用的
|
||||
question_useful: 这是有用和清楚的
|
||||
question_un_useful: 它不明确或没有用
|
||||
answer_useful: 这是有用的
|
||||
answer_un_useful: 它是没有用的
|
||||
answers:
|
||||
title: 个回答
|
||||
score: 评分
|
||||
|
@ -792,16 +859,19 @@ ui:
|
|||
empty: 回答内容不能为空。
|
||||
characters: 内容长度至少 6 个字符
|
||||
tips:
|
||||
header_1: Thanks for your answer
|
||||
li1_1: Please be sure to <strong>answer the question</strong>. Provide details and share your research.
|
||||
li1_2: Back up any statements you make with references or personal experience.
|
||||
header_2: But <strong>avoid</strong> ...
|
||||
li2_1: Asking for help, seeking clarification, or responding to other answers.
|
||||
header_1: 感谢你的回答.
|
||||
li1_1: 请务必 <strong>回答问题</strong>。提供详细信息并分享您的研究。
|
||||
li1_2: 用参考资料或个人经历来支持你所做的任何陈述。
|
||||
header_2: 但是 <strong>请避免</strong>...
|
||||
li2_1: 请求帮助,寻求澄清,或答复其他答案。
|
||||
reopen:
|
||||
confirm_btn: 重新打开
|
||||
title: 重新打开这个帖子
|
||||
content: 确定要重新打开吗?
|
||||
success: 这个帖子已被重新打开
|
||||
pin:
|
||||
title: 置顶此条帖子
|
||||
content: 你确定要全局置顶吗?这个帖子将出现在所有帖子列表的顶部。
|
||||
confirm_btn: 置顶
|
||||
delete:
|
||||
title: 删除
|
||||
question: >-
|
||||
|
@ -809,7 +879,6 @@ ui:
|
|||
answer_accepted: >-
|
||||
<p>我们不建议<strong>删除被采纳的回答</strong>。因为这样做会使得后来的读者无法从该回答中获得帮助。</p>如果删除过多被采纳的回答,你的账号将会被禁止回答任何提问。你确定要删除吗?
|
||||
other: 你确定要删除?
|
||||
tip_question_deleted: 此问题已被删除
|
||||
tip_answer_deleted: 此回答已被删除
|
||||
btns:
|
||||
confirm: 确认
|
||||
|
@ -825,6 +894,7 @@ ui:
|
|||
reject: 拒绝
|
||||
skip: 略过
|
||||
discard_draft: 丢弃草稿
|
||||
pinned: 已置顶
|
||||
search:
|
||||
title: 搜索结果
|
||||
keywords: 关键词
|
||||
|
@ -1014,11 +1084,11 @@ ui:
|
|||
answers: 个回答
|
||||
accepted: 已被采纳
|
||||
page_error:
|
||||
http_error: HTTP Error {{ code }}
|
||||
desc_403: You don’t have permission to access this page.
|
||||
desc_404: Unfortunately, this page doesn't exist.
|
||||
desc_50X: The server encountered an error and could not complete your request.
|
||||
back_home: Back to homepage
|
||||
http_error: HTTP 错误 {{ code }}
|
||||
desc_403: 您没有权限访问此页面。
|
||||
desc_404: 很抱歉,此页面不存在。
|
||||
desc_50X: 服务器遇到了一个错误,无法完成你的请求。
|
||||
back_home: 返回首页
|
||||
page_maintenance:
|
||||
desc: "我们正在进行维护,我们将很快回来。"
|
||||
nav_menus:
|
||||
|
@ -1046,6 +1116,9 @@ ui:
|
|||
installed_plugins: 插件列表
|
||||
website_welcome: 欢迎来到 {{site_name}}
|
||||
plugins:
|
||||
login: 登录
|
||||
qrcode_login_tip: 请使用 {{ agentName }} 扫描二维码登录
|
||||
login_failed_email_tip: 登录失败, 请允许该应用程序访问您的电子邮件信息,然后再试一次。
|
||||
oauth:
|
||||
connect: 连接到 {{ auth_name }}
|
||||
remove: 解绑 {{ auth_name }}
|
||||
|
@ -1410,6 +1483,10 @@ ui:
|
|||
closed: 关闭
|
||||
reopened: 重新开启
|
||||
created: 创建于
|
||||
pin: 已置顶
|
||||
unpin: 取消置頂
|
||||
show: 已显示
|
||||
hide: 已隐藏
|
||||
title: "历史记录"
|
||||
tag_title: "时间线"
|
||||
show_votes: "显示投票"
|
||||
|
@ -1424,8 +1501,8 @@ ui:
|
|||
no_data: "空空如也"
|
||||
users:
|
||||
title: 用户
|
||||
users_with_the_most_reputation: Users with the highest reputation scores this week
|
||||
users_with_the_most_vote: Users who voted the most this week
|
||||
users_with_the_most_reputation: 本周声望最高的用户
|
||||
users_with_the_most_vote: 本周投票最多的用户
|
||||
staffs: 我们的社区工作人员
|
||||
reputation: 声望值
|
||||
votes: 投票
|
||||
|
@ -1436,4 +1513,8 @@ ui:
|
|||
discard_confirm: 您确定要丢弃您的草稿吗?
|
||||
messages:
|
||||
post_deleted: 该帖子已被删除。
|
||||
|
||||
post_pin: 该帖子已被置顶。
|
||||
post_unpin: 该帖子已被取消置顶。
|
||||
post_hide_list: 此帖子已经从列表中隐藏。
|
||||
post_show_list: 该帖子已显示到列表中。
|
||||
post_reopen: 这个帖子已被重新打开.
|
||||
|
|
|
@ -22,6 +22,14 @@ backend:
|
|||
other: Close
|
||||
reopen:
|
||||
other: Reopen
|
||||
pin:
|
||||
other: Pin
|
||||
hide:
|
||||
other: Unlist
|
||||
unpin:
|
||||
other: Unpin
|
||||
show:
|
||||
other: List
|
||||
role:
|
||||
name:
|
||||
user:
|
||||
|
@ -759,6 +767,7 @@ ui:
|
|||
btn: 發問
|
||||
answers: 個回答
|
||||
question_detail:
|
||||
action: Action
|
||||
Asked: 提問於
|
||||
asked: 提問於
|
||||
update: 修改於
|
||||
|
@ -800,7 +809,10 @@ ui:
|
|||
confirm_btn: Reopen
|
||||
title: 重新打開這個貼文
|
||||
content: 確定要重新打開嗎?
|
||||
success: 這個貼文已被重新打開
|
||||
pin:
|
||||
title: Pin this post
|
||||
content: Are you sure you wish to pinned globally? This post will appear at the top of all post lists.
|
||||
confirm_btn: Pin
|
||||
delete:
|
||||
title: 刪除此貼
|
||||
question: >-
|
||||
|
@ -808,7 +820,6 @@ ui:
|
|||
answer_accepted: >-
|
||||
<p>我們不建議<strong>刪除被採納的回答</strong>。因為這樣做會使得後來的讀者無法從該回答中獲得幫助。</p>如果刪除過多被採納的貼文,你的帳號將會被禁止回答任何提問。你確定要刪除嗎?
|
||||
other: 你確定要刪除?
|
||||
tip_question_deleted: 該帖子已被刪除。
|
||||
tip_answer_deleted: 此回答已被刪除
|
||||
btns:
|
||||
confirm: 確認
|
||||
|
@ -824,6 +835,7 @@ ui:
|
|||
reject: 拒絕
|
||||
skip: 略過
|
||||
discard_draft: Discard draft
|
||||
pinned: Pinned
|
||||
search:
|
||||
title: 搜尋結果
|
||||
keywords: 關鍵詞
|
||||
|
@ -1385,6 +1397,10 @@ ui:
|
|||
closed: 關閉
|
||||
reopened: 重新開啟
|
||||
created: 創建於
|
||||
pin: pinned
|
||||
unpin: unpinned
|
||||
show: listed
|
||||
hide: unlisted
|
||||
title: "歷史記錄"
|
||||
tag_title: "時間線"
|
||||
show_votes: "顯示投票"
|
||||
|
@ -1411,4 +1427,8 @@ ui:
|
|||
discard_confirm: Are you sure you want to discard your draft?
|
||||
messages:
|
||||
post_deleted: This post has been deleted.
|
||||
|
||||
post_pin: This post has been pinned.
|
||||
post_unpin: This post has been unpinned.
|
||||
post_hide_list: This post has been hidden from list.
|
||||
post_show_list: This post has been shown to list.
|
||||
post_reopen: This post has been reopened.
|
||||
|
|
|
@ -14,6 +14,10 @@ const (
|
|||
ActFollow = "follow"
|
||||
ActAccepted = "accepted"
|
||||
ActAccept = "accept"
|
||||
ActPin = "pin"
|
||||
ActUnPin = "unpin"
|
||||
ActShow = "show"
|
||||
ActHide = "hide"
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -29,6 +33,10 @@ const (
|
|||
ActQuestionRollback ActivityTypeKey = "question.rollback"
|
||||
ActQuestionDeleted ActivityTypeKey = "question.deleted"
|
||||
ActQuestionUndeleted ActivityTypeKey = "question.undeleted"
|
||||
ActQuestionPin ActivityTypeKey = "question.pin"
|
||||
ActQuestionUnPin ActivityTypeKey = "question.unpin"
|
||||
ActQuestionHide ActivityTypeKey = "question.hide"
|
||||
ActQuestionShow ActivityTypeKey = "question.show"
|
||||
)
|
||||
|
||||
const (
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
package constant
|
||||
|
||||
import "github.com/answerdev/answer/internal/base/reason"
|
||||
|
||||
type Privilege struct {
|
||||
Key string `json:"key"`
|
||||
Label string `json:"label"`
|
||||
Value int `json:"value"`
|
||||
Key string `json:"-"`
|
||||
}
|
||||
|
||||
const (
|
||||
|
@ -41,80 +43,28 @@ const (
|
|||
RankTagUseReservedTagKey = "rank.tag.use_reserved_tag"
|
||||
)
|
||||
|
||||
//| Permission | Level 1 | Level 2 | Level 3 | Custom Level |
|
||||
//| -------------------------------------- | ------------------------------------------------ | --------------------------------------------- | --------------------------------------------- | ------------ |
|
||||
//| Description | less reputation required for private team, group | low reputation required for startup community | high reputation required for mature community | |
|
||||
//| Ask question | 1 | 1 | 1 | |
|
||||
//| Write answer | 1 | 1 | 1 | |
|
||||
//| Write comment | 1 | 1 | 1 | |
|
||||
//| Accept answer | 1 | 1 | 1 | |
|
||||
//| Flag | 1 | 1 | 1 | |
|
||||
//| Upvote comment | 1 | 1 | 1 | |
|
||||
//| Post more than 2 links at a time | 1 | 10 | 10 | |
|
||||
//| Upvote question | 1 | 1 | 15 | |
|
||||
//| Upvote answer | 1 | 1 | 15 | |
|
||||
//| Downvote question | 125 | 125 | 125 | |
|
||||
//| Downvote answer | 125 | 125 | 125 | |
|
||||
//| Create new tag | 1 | 750 | 1500 | |
|
||||
//| Edit tag description (need to review) | 1 | 50 | 100 | |
|
||||
//| Edit other's question (need to review) | 1 | 100 | 200 | |
|
||||
//| Edit other's answer (need to review) | 1 | 100 | 200 | |
|
||||
//| Edit other's question without review | 1 | 1000 | 2000 | |
|
||||
//| Edit other's answer without review | 1 | 1000 | 2000 | |
|
||||
//| Revew question edits | 1 | 1000 | 2000 | |
|
||||
//| Review answer edits | 1 | 1000 | 2000 | |
|
||||
//| Review tag edits | 1 | 2500 | 5000 | |
|
||||
//| Edit tag description without review | 1 | 10000 | 20000 | |
|
||||
//| Manage tag synonyms | 1 | 10000 | 20000 | |
|
||||
|
||||
const (
|
||||
RankQuestionAddLabel = "Ask question"
|
||||
RankAnswerAddLabel = "Write answer"
|
||||
RankCommentAddLabel = "Write comment"
|
||||
RankAnswerAcceptLabel = "Accept answer"
|
||||
RankReportAddLabel = "Flag"
|
||||
RankCommentVoteUpLabel = "Upvote comment"
|
||||
RankLinkUrlLimitLabel = "Post more than 2 links at a time"
|
||||
RankQuestionVoteUpLabel = "Upvote question"
|
||||
RankAnswerVoteUpLabel = "Upvote answer"
|
||||
RankQuestionVoteDownLabel = "Downvote question"
|
||||
RankAnswerVoteDownLabel = "Downvote answer"
|
||||
RankTagAddLabel = "Create new tag"
|
||||
RankTagEditLabel = "Edit tag description (need to review)"
|
||||
RankQuestionEditLabel = "Edit other's question (need to review)"
|
||||
RankAnswerEditLabel = "Edit other's answer (need to review)"
|
||||
RankQuestionEditWithoutReviewLabel = "Edit other's question without review"
|
||||
RankAnswerEditWithoutReviewLabel = "Edit other's answer without review"
|
||||
RankQuestionAuditLabel = "Review question edits"
|
||||
RankAnswerAuditLabel = "Review answer edits"
|
||||
RankTagAuditLabel = "Review tag edits"
|
||||
RankTagEditWithoutReviewLabel = "Edit tag description without review"
|
||||
RankTagSynonymLabel = "Manage tag synonyms"
|
||||
)
|
||||
|
||||
var (
|
||||
RankAllPrivileges = []*Privilege{
|
||||
{Label: RankQuestionAddLabel, Key: RankQuestionAddKey},
|
||||
{Label: RankAnswerAddLabel, Key: RankAnswerAddKey},
|
||||
{Label: RankCommentAddLabel, Key: RankCommentAddKey},
|
||||
{Label: RankAnswerAcceptLabel, Key: RankAnswerAcceptKey},
|
||||
{Label: RankReportAddLabel, Key: RankReportAddKey},
|
||||
{Label: RankCommentVoteUpLabel, Key: RankCommentVoteUpKey},
|
||||
{Label: RankLinkUrlLimitLabel, Key: RankLinkUrlLimitKey},
|
||||
{Label: RankQuestionVoteUpLabel, Key: RankQuestionVoteUpKey},
|
||||
{Label: RankAnswerVoteUpLabel, Key: RankAnswerVoteUpKey},
|
||||
{Label: RankQuestionVoteDownLabel, Key: RankQuestionVoteDownKey},
|
||||
{Label: RankAnswerVoteDownLabel, Key: RankAnswerVoteDownKey},
|
||||
{Label: RankTagAddLabel, Key: RankTagAddKey},
|
||||
{Label: RankTagEditLabel, Key: RankTagEditKey},
|
||||
{Label: RankQuestionEditLabel, Key: RankQuestionEditKey},
|
||||
{Label: RankAnswerEditLabel, Key: RankAnswerEditKey},
|
||||
{Label: RankQuestionEditWithoutReviewLabel, Key: RankQuestionEditWithoutReviewKey},
|
||||
{Label: RankAnswerEditWithoutReviewLabel, Key: RankAnswerEditWithoutReviewKey},
|
||||
{Label: RankQuestionAuditLabel, Key: RankQuestionAuditKey},
|
||||
{Label: RankAnswerAuditLabel, Key: RankAnswerAuditKey},
|
||||
{Label: RankTagAuditLabel, Key: RankTagAuditKey},
|
||||
{Label: RankTagEditWithoutReviewLabel, Key: RankTagEditWithoutReviewKey},
|
||||
{Label: RankTagSynonymLabel, Key: RankTagSynonymKey},
|
||||
{Label: reason.RankQuestionAddLabel, Key: RankQuestionAddKey},
|
||||
{Label: reason.RankAnswerAddLabel, Key: RankAnswerAddKey},
|
||||
{Label: reason.RankCommentAddLabel, Key: RankCommentAddKey},
|
||||
{Label: reason.RankReportAddLabel, Key: RankReportAddKey},
|
||||
{Label: reason.RankCommentVoteUpLabel, Key: RankCommentVoteUpKey},
|
||||
{Label: reason.RankLinkUrlLimitLabel, Key: RankLinkUrlLimitKey},
|
||||
{Label: reason.RankQuestionVoteUpLabel, Key: RankQuestionVoteUpKey},
|
||||
{Label: reason.RankAnswerVoteUpLabel, Key: RankAnswerVoteUpKey},
|
||||
{Label: reason.RankQuestionVoteDownLabel, Key: RankQuestionVoteDownKey},
|
||||
{Label: reason.RankAnswerVoteDownLabel, Key: RankAnswerVoteDownKey},
|
||||
{Label: reason.RankTagAddLabel, Key: RankTagAddKey},
|
||||
{Label: reason.RankTagEditLabel, Key: RankTagEditKey},
|
||||
{Label: reason.RankQuestionEditLabel, Key: RankQuestionEditKey},
|
||||
{Label: reason.RankAnswerEditLabel, Key: RankAnswerEditKey},
|
||||
{Label: reason.RankQuestionEditWithoutReviewLabel, Key: RankQuestionEditWithoutReviewKey},
|
||||
{Label: reason.RankAnswerEditWithoutReviewLabel, Key: RankAnswerEditWithoutReviewKey},
|
||||
{Label: reason.RankQuestionAuditLabel, Key: RankQuestionAuditKey},
|
||||
{Label: reason.RankAnswerAuditLabel, Key: RankAnswerAuditKey},
|
||||
{Label: reason.RankTagAuditLabel, Key: RankTagAuditKey},
|
||||
{Label: reason.RankTagEditWithoutReviewLabel, Key: RankTagEditWithoutReviewKey},
|
||||
{Label: reason.RankTagSynonymLabel, Key: RankTagSynonymKey},
|
||||
}
|
||||
)
|
||||
|
|
|
@ -8,9 +8,13 @@ import (
|
|||
"github.com/segmentfault/pacman/errors"
|
||||
)
|
||||
|
||||
// BanAPIWhenUserCenterEnabled ban api when user center enabled
|
||||
func BanAPIWhenUserCenterEnabled(ctx *gin.Context) {
|
||||
if plugin.UserCenterEnabled() {
|
||||
// BanAPIForUserCenter ban api for user center
|
||||
func BanAPIForUserCenter(ctx *gin.Context) {
|
||||
uc, ok := plugin.GetUserCenter()
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
if !uc.Description().EnabledOriginalUserSystem {
|
||||
handler.HandleResponse(ctx, errors.Forbidden(reason.ForbiddenError), nil)
|
||||
ctx.Abort()
|
||||
return
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
package reason
|
||||
|
||||
const (
|
||||
PrivilegeLevel1Desc = "privilege.level_1.description"
|
||||
PrivilegeLevel2Desc = "privilege.level_2.description"
|
||||
PrivilegeLevel3Desc = "privilege.level_3.description"
|
||||
|
||||
RankQuestionAddLabel = "privilege.rank_question_add_label"
|
||||
RankAnswerAddLabel = "privilege.rank_answer_add_label"
|
||||
RankCommentAddLabel = "privilege.rank_comment_add_label"
|
||||
RankReportAddLabel = "privilege.rank_report_add_label"
|
||||
RankCommentVoteUpLabel = "privilege.rank_comment_vote_up_label"
|
||||
RankLinkUrlLimitLabel = "privilege.rank_link_url_limit_label"
|
||||
RankQuestionVoteUpLabel = "privilege.rank_question_vote_up_label"
|
||||
RankAnswerVoteUpLabel = "privilege.rank_answer_vote_up_label"
|
||||
RankQuestionVoteDownLabel = "privilege.rank_question_vote_down_label"
|
||||
RankAnswerVoteDownLabel = "privilege.rank_answer_vote_down_label"
|
||||
RankTagAddLabel = "privilege.rank_tag_add_label"
|
||||
RankTagEditLabel = "privilege.rank_tag_edit_label"
|
||||
RankQuestionEditLabel = "privilege.rank_question_edit_label"
|
||||
RankAnswerEditLabel = "privilege.rank_answer_edit_label"
|
||||
RankQuestionEditWithoutReviewLabel = "privilege.rank_question_edit_without_review_label"
|
||||
RankAnswerEditWithoutReviewLabel = "privilege.rank_answer_edit_without_review_label"
|
||||
RankQuestionAuditLabel = "privilege.rank_question_audit_label"
|
||||
RankAnswerAuditLabel = "privilege.rank_answer_audit_label"
|
||||
RankTagAuditLabel = "privilege.rank_tag_audit_label"
|
||||
RankTagEditWithoutReviewLabel = "privilege.rank_tag_edit_without_review_label"
|
||||
RankTagSynonymLabel = "privilege.rank_tag_synonym_label"
|
||||
)
|
|
@ -74,4 +74,6 @@ const (
|
|||
AdminCannotUpdateTheirPassword = "error.admin.cannot_update_their_password"
|
||||
AdminCannotModifySelfStatus = "error.admin.cannot_modify_self_status"
|
||||
UserExternalLoginUnbindingForbidden = "error.user.external_login_unbinding_forbidden"
|
||||
UserAccessDenied = "error.user.access_denied"
|
||||
UserPageAccessDenied = "error.user.page_access_denied"
|
||||
)
|
||||
|
|
|
@ -68,6 +68,7 @@ func NewHTTPServer(debug bool,
|
|||
// plugin routes
|
||||
pluginAPIRouter.RegisterUnAuthConnectorRouter(mustUnAuthV1)
|
||||
pluginAPIRouter.RegisterAuthUserConnectorRouter(authV1)
|
||||
pluginAPIRouter.RegisterAuthAdminConnectorRouter(adminauthV1)
|
||||
|
||||
_ = plugin.CallAgent(func(agent plugin.Agent) error {
|
||||
agent.RegisterUnAuthRouter(unAuthV1)
|
||||
|
|
|
@ -130,7 +130,7 @@ func (cc *ConnectorController) ConnectorRedirect(connector plugin.Connector) (fn
|
|||
return
|
||||
}
|
||||
if len(resp.AccessToken) > 0 {
|
||||
ctx.Redirect(http.StatusFound, fmt.Sprintf("%s/users/oauth?access_token=%s",
|
||||
ctx.Redirect(http.StatusFound, fmt.Sprintf("%s/users/auth-landing?access_token=%s",
|
||||
siteGeneral.SiteUrl, resp.AccessToken))
|
||||
} else {
|
||||
ctx.Redirect(http.StatusFound, fmt.Sprintf("%s/users/confirm-email?binding_key=%s",
|
||||
|
|
|
@ -3,8 +3,6 @@ package controller
|
|||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"github.com/answerdev/answer/internal/base/handler"
|
||||
"github.com/answerdev/answer/internal/base/middleware"
|
||||
|
@ -62,9 +60,11 @@ func (uc *UserCenterController) UserCenterAgent(ctx *gin.Context) {
|
|||
_ = plugin.CallUserCenter(func(uc plugin.UserCenter) error {
|
||||
info := uc.Description()
|
||||
resp.AgentInfo.Name = info.Name
|
||||
resp.AgentInfo.DisplayName = info.DisplayName.Translate(ctx)
|
||||
resp.AgentInfo.Icon = info.Icon
|
||||
resp.AgentInfo.Url = info.Url
|
||||
resp.AgentInfo.ControlCenterItems = make([]*schema.ControlCenter, 0)
|
||||
resp.AgentInfo.EnabledOriginalUserSystem = info.EnabledOriginalUserSystem
|
||||
items := uc.ControlCenterItems()
|
||||
for _, item := range items {
|
||||
resp.AgentInfo.ControlCenterItems = append(resp.AgentInfo.ControlCenterItems, &schema.ControlCenter{
|
||||
|
@ -126,7 +126,9 @@ func (uc *UserCenterController) UserCenterLoginCallback(ctx *gin.Context) {
|
|||
userInfo, err := userCenter.LoginCallback(ctx)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
ctx.Redirect(http.StatusFound, "/50x")
|
||||
if !ctx.IsAborted() {
|
||||
ctx.Redirect(http.StatusFound, "/50x")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -137,11 +139,11 @@ func (uc *UserCenterController) UserCenterLoginCallback(ctx *gin.Context) {
|
|||
return
|
||||
}
|
||||
if len(resp.ErrMsg) > 0 {
|
||||
ctx.String(http.StatusOK, resp.ErrMsg)
|
||||
ctx.Redirect(http.StatusFound, fmt.Sprintf("/50x?title=%s&msg=%s", resp.ErrTitle, resp.ErrMsg))
|
||||
return
|
||||
}
|
||||
userCenter.AfterLogin(userInfo.ExternalID, resp.AccessToken)
|
||||
ctx.Redirect(http.StatusFound, fmt.Sprintf("%s/users/oauth?access_token=%s",
|
||||
ctx.Redirect(http.StatusFound, fmt.Sprintf("%s/users/auth-landing?access_token=%s",
|
||||
siteGeneral.SiteUrl, resp.AccessToken))
|
||||
}
|
||||
|
||||
|
@ -172,11 +174,11 @@ func (uc *UserCenterController) UserCenterSignUpCallback(ctx *gin.Context) {
|
|||
return
|
||||
}
|
||||
if len(resp.ErrMsg) > 0 {
|
||||
ctx.String(http.StatusOK, resp.ErrMsg)
|
||||
ctx.Redirect(http.StatusFound, fmt.Sprintf("/50x?title=%s&msg=%s", resp.ErrTitle, resp.ErrMsg))
|
||||
return
|
||||
}
|
||||
userCenter.AfterLogin(userInfo.ExternalID, resp.AccessToken)
|
||||
ctx.Redirect(http.StatusFound, fmt.Sprintf("%s/users/oauth?access_token=%s",
|
||||
ctx.Redirect(http.StatusFound, fmt.Sprintf("%s/users/auth-landing?access_token=%s",
|
||||
siteGeneral.SiteUrl, resp.AccessToken))
|
||||
}
|
||||
|
||||
|
@ -192,17 +194,3 @@ func (uc *UserCenterController) UserCenterAdminFunctionAgent(ctx *gin.Context) {
|
|||
resp, err := uc.userCenterLoginService.UserCenterAdminFunctionAgent(ctx)
|
||||
handler.HandleResponse(ctx, err, resp)
|
||||
}
|
||||
|
||||
func (uc *UserCenterController) formatRedirectURL(ctx *gin.Context, redirectURL string) string {
|
||||
if !strings.Contains(redirectURL, "CALLBACK_URL") {
|
||||
return redirectURL
|
||||
}
|
||||
general, err := uc.siteInfoService.GetSiteGeneral(ctx)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
ctx.Redirect(http.StatusFound, "/50x")
|
||||
return ""
|
||||
}
|
||||
callbackURL := fmt.Sprintf("%s%s%s", general.SiteUrl, commonRouterPrefix, "/user-center/login/callback")
|
||||
return strings.ReplaceAll(redirectURL, "CALLBACK_URL", url.QueryEscape(callbackURL))
|
||||
}
|
||||
|
|
|
@ -70,6 +70,47 @@ func (qc *QuestionController) RemoveQuestion(ctx *gin.Context) {
|
|||
handler.HandleResponse(ctx, err, nil)
|
||||
}
|
||||
|
||||
// OperationQuestion Operation question
|
||||
// @Summary Operation question
|
||||
// @Description Operation question \n operation [pin unpin hide show]
|
||||
// @Tags Question
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security ApiKeyAuth
|
||||
// @Param data body schema.OperationQuestionReq true "question"
|
||||
// @Success 200 {object} handler.RespBody
|
||||
// @Router /answer/api/v1/question/operation [put]
|
||||
func (qc *QuestionController) OperationQuestion(ctx *gin.Context) {
|
||||
req := &schema.OperationQuestionReq{}
|
||||
if handler.BindAndCheck(ctx, req) {
|
||||
return
|
||||
}
|
||||
req.ID = uid.DeShortID(req.ID)
|
||||
req.UserID = middleware.GetLoginUserIDFromContext(ctx)
|
||||
canList, err := qc.rankService.CheckOperationPermissions(ctx, req.UserID, []string{
|
||||
permission.QuestionPin,
|
||||
permission.QuestionUnPin,
|
||||
permission.QuestionHide,
|
||||
permission.QuestionShow,
|
||||
})
|
||||
if err != nil {
|
||||
handler.HandleResponse(ctx, err, nil)
|
||||
return
|
||||
}
|
||||
req.CanPin = canList[0]
|
||||
req.CanList = canList[1]
|
||||
if (req.Operation == schema.QuestionOperationPin || req.Operation == schema.QuestionOperationUnPin) && !req.CanPin {
|
||||
handler.HandleResponse(ctx, errors.Forbidden(reason.RankFailToMeetTheCondition), nil)
|
||||
return
|
||||
}
|
||||
if (req.Operation == schema.QuestionOperationHide || req.Operation == schema.QuestionOperationShow) && !req.CanList {
|
||||
handler.HandleResponse(ctx, errors.Forbidden(reason.RankFailToMeetTheCondition), nil)
|
||||
return
|
||||
}
|
||||
err = qc.questionService.OperationQuestion(ctx, req)
|
||||
handler.HandleResponse(ctx, err, nil)
|
||||
}
|
||||
|
||||
// CloseQuestion Close question
|
||||
// @Summary Close question
|
||||
// @Description Close question
|
||||
|
@ -152,6 +193,10 @@ func (qc *QuestionController) GetQuestion(ctx *gin.Context) {
|
|||
permission.QuestionDelete,
|
||||
permission.QuestionClose,
|
||||
permission.QuestionReopen,
|
||||
permission.QuestionPin,
|
||||
permission.QuestionUnPin,
|
||||
permission.QuestionHide,
|
||||
permission.QuestionShow,
|
||||
})
|
||||
if err != nil {
|
||||
handler.HandleResponse(ctx, err, nil)
|
||||
|
@ -163,6 +208,10 @@ func (qc *QuestionController) GetQuestion(ctx *gin.Context) {
|
|||
req.CanDelete = canList[1]
|
||||
req.CanClose = canList[2]
|
||||
req.CanReopen = canList[3]
|
||||
req.CanPin = canList[4]
|
||||
req.CanUnPin = canList[5]
|
||||
req.CanHide = canList[6]
|
||||
req.CanShow = canList[7]
|
||||
|
||||
info, err := qc.questionService.GetQuestionAndAddPV(ctx, id, userID, req)
|
||||
if err != nil {
|
||||
|
|
|
@ -32,7 +32,7 @@ func NewUserAdminController(userService *user_admin.UserAdminService) *UserAdmin
|
|||
// @Success 200 {object} handler.RespBody
|
||||
// @Router /answer/admin/api/user/status [put]
|
||||
func (uc *UserAdminController) UpdateUserStatus(ctx *gin.Context) {
|
||||
if plugin.UserCenterEnabled() {
|
||||
if u, ok := plugin.GetUserCenter(); ok && u.Description().UserStatusAgentEnabled {
|
||||
handler.HandleResponse(ctx, errors.Forbidden(reason.ForbiddenError), nil)
|
||||
return
|
||||
}
|
||||
|
|
|
@ -6,4 +6,5 @@ type UserCacheInfo struct {
|
|||
UserStatus int `json:"user_status"`
|
||||
EmailStatus int `json:"email_status"`
|
||||
RoleID int `json:"role_id"`
|
||||
ExternalID string `json:"external_id"`
|
||||
}
|
||||
|
|
|
@ -8,6 +8,10 @@ const (
|
|||
QuestionStatusAvailable = 1
|
||||
QuestionStatusClosed = 2
|
||||
QuestionStatusDeleted = 10
|
||||
QuestionUnPin = 1
|
||||
QuestionPin = 2
|
||||
QuestionShow = 1
|
||||
QuestionHide = 2
|
||||
)
|
||||
|
||||
var AdminQuestionSearchStatus = map[string]int{
|
||||
|
@ -32,6 +36,8 @@ type Question struct {
|
|||
Title string `xorm:"not null default '' VARCHAR(150) title"`
|
||||
OriginalText string `xorm:"not null MEDIUMTEXT original_text"`
|
||||
ParsedText string `xorm:"not null MEDIUMTEXT parsed_text"`
|
||||
Pin int `xorm:"not null default 1 INT(11) pin"`
|
||||
Show int `xorm:"not null default 1 INT(11) show"`
|
||||
Status int `xorm:"not null default 1 INT(11) status"`
|
||||
ViewCount int `xorm:"not null default 0 INT(11) view_count"`
|
||||
UniqueViewCount int `xorm:"not null default 0 INT(11) unique_view_count"`
|
||||
|
|
|
@ -179,6 +179,25 @@ func initSiteInfo(engine *xorm.Engine, language, siteName, siteURL, contactEmail
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
usersData := map[string]any{
|
||||
"default_avatar": "gravatar",
|
||||
"allow_update_display_name": true,
|
||||
"allow_update_username": true,
|
||||
"allow_update_avatar": true,
|
||||
"allow_update_bio": true,
|
||||
"allow_update_website": true,
|
||||
"allow_update_location": true,
|
||||
}
|
||||
usersDataBytes, _ := json.Marshal(usersData)
|
||||
_, err = engine.InsertOne(&entity.SiteInfo{
|
||||
Type: "users",
|
||||
Content: string(usersDataBytes),
|
||||
Status: 1,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
|
@ -272,7 +291,7 @@ func initConfigTable(engine *xorm.Engine) error {
|
|||
{ID: 41, Key: "rank.answer.add", Value: `1`},
|
||||
{ID: 42, Key: "rank.answer.edit", Value: `200`},
|
||||
{ID: 43, Key: "rank.answer.delete", Value: `-1`},
|
||||
{ID: 44, Key: "rank.answer.accept", Value: `1`},
|
||||
{ID: 44, Key: "rank.answer.accept", Value: `-1`},
|
||||
{ID: 45, Key: "rank.answer.vote_up", Value: `15`},
|
||||
{ID: 46, Key: "rank.answer.vote_down", Value: `125`},
|
||||
{ID: 47, Key: "rank.comment.add", Value: `1`},
|
||||
|
@ -347,6 +366,10 @@ func initConfigTable(engine *xorm.Engine) error {
|
|||
{ID: 116, Key: "rank.question.reopen", Value: `-1`},
|
||||
{ID: 117, Key: "rank.tag.use_reserved_tag", Value: `-1`},
|
||||
{ID: 118, Key: "plugin.status", Value: `{}`},
|
||||
{ID: 119, Key: "question.pin", Value: `-1`},
|
||||
{ID: 120, Key: "question.unpin", Value: `-1`},
|
||||
{ID: 121, Key: "question.show", Value: `-1`},
|
||||
{ID: 122, Key: "question.hide", Value: `-1`},
|
||||
}
|
||||
_, err := engine.Insert(defaultConfigTable)
|
||||
return err
|
||||
|
@ -397,6 +420,10 @@ func initRolePower(engine *xorm.Engine) (err error) {
|
|||
{ID: 31, Name: "answer audit", PowerType: permission.AnswerAudit, Description: "answer audit"},
|
||||
{ID: 32, Name: "question audit", PowerType: permission.QuestionAudit, Description: "question audit"},
|
||||
{ID: 33, Name: "tag audit", PowerType: permission.TagAudit, Description: "tag audit"},
|
||||
{ID: 34, Name: "question pin", PowerType: permission.QuestionPin, Description: "top the question"},
|
||||
{ID: 35, Name: "question hide", PowerType: permission.QuestionHide, Description: "hide the question"},
|
||||
{ID: 36, Name: "question unpin", PowerType: permission.QuestionUnPin, Description: "untop the question"},
|
||||
{ID: 37, Name: "question show", PowerType: permission.QuestionShow, Description: "show the question"},
|
||||
}
|
||||
_, err = engine.Insert(powers)
|
||||
if err != nil {
|
||||
|
@ -438,6 +465,10 @@ func initRolePower(engine *xorm.Engine) (err error) {
|
|||
{RoleID: 2, PowerType: permission.QuestionAudit},
|
||||
{RoleID: 2, PowerType: permission.TagAudit},
|
||||
{RoleID: 2, PowerType: permission.TagUseReservedTag},
|
||||
{RoleID: 2, PowerType: permission.QuestionPin},
|
||||
{RoleID: 2, PowerType: permission.QuestionHide},
|
||||
{RoleID: 2, PowerType: permission.QuestionUnPin},
|
||||
{RoleID: 2, PowerType: permission.QuestionShow},
|
||||
|
||||
{RoleID: 3, PowerType: permission.QuestionAdd},
|
||||
{RoleID: 3, PowerType: permission.QuestionEdit},
|
||||
|
@ -472,6 +503,10 @@ func initRolePower(engine *xorm.Engine) (err error) {
|
|||
{RoleID: 3, PowerType: permission.QuestionAudit},
|
||||
{RoleID: 3, PowerType: permission.TagAudit},
|
||||
{RoleID: 3, PowerType: permission.TagUseReservedTag},
|
||||
{RoleID: 3, PowerType: permission.QuestionPin},
|
||||
{RoleID: 3, PowerType: permission.QuestionHide},
|
||||
{RoleID: 3, PowerType: permission.QuestionUnPin},
|
||||
{RoleID: 3, PowerType: permission.QuestionShow},
|
||||
}
|
||||
_, err = engine.Insert(rolePowerRels)
|
||||
if err != nil {
|
||||
|
|
|
@ -56,6 +56,8 @@ var migrations = []Migration{
|
|||
NewMigration("add user role", addRoleFeatures, false),
|
||||
NewMigration("add theme and private mode", addThemeAndPrivateMode, true),
|
||||
NewMigration("add new answer notification", addNewAnswerNotification, true),
|
||||
NewMigration("add user pin hide features", addRolePinAndHideFeatures, true),
|
||||
NewMigration("update accept answer rank", updateAcceptAnswerRank, true),
|
||||
NewMigration("add plugin", addPlugin, false),
|
||||
NewMigration("add login limitations", addLoginLimitations, true),
|
||||
}
|
||||
|
|
|
@ -0,0 +1,69 @@
|
|||
package migrations
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/answerdev/answer/internal/base/constant"
|
||||
"github.com/answerdev/answer/internal/entity"
|
||||
"github.com/answerdev/answer/internal/schema"
|
||||
"github.com/tidwall/gjson"
|
||||
"xorm.io/xorm"
|
||||
)
|
||||
|
||||
func addLoginLimitations(x *xorm.Engine) error {
|
||||
loginSiteInfo := &entity.SiteInfo{
|
||||
Type: constant.SiteTypeLogin,
|
||||
}
|
||||
exist, err := x.Get(loginSiteInfo)
|
||||
if err != nil {
|
||||
return fmt.Errorf("get config failed: %w", err)
|
||||
}
|
||||
if exist {
|
||||
content := &schema.SiteLoginReq{}
|
||||
_ = json.Unmarshal([]byte(loginSiteInfo.Content), content)
|
||||
content.AllowEmailRegistrations = true
|
||||
content.AllowEmailDomains = make([]string, 0)
|
||||
_, err = x.ID(loginSiteInfo.ID).Cols("content").Update(loginSiteInfo)
|
||||
if err != nil {
|
||||
return fmt.Errorf("update site info failed: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
interfaceSiteInfo := &entity.SiteInfo{
|
||||
Type: constant.SiteTypeInterface,
|
||||
}
|
||||
exist, err = x.Get(interfaceSiteInfo)
|
||||
if err != nil {
|
||||
return fmt.Errorf("get config failed: %w", err)
|
||||
}
|
||||
siteUsers := &schema.SiteUsersReq{
|
||||
AllowUpdateDisplayName: true,
|
||||
AllowUpdateUsername: true,
|
||||
AllowUpdateAvatar: true,
|
||||
AllowUpdateBio: true,
|
||||
AllowUpdateWebsite: true,
|
||||
AllowUpdateLocation: true,
|
||||
}
|
||||
if exist {
|
||||
siteUsers.DefaultAvatar = gjson.Get(interfaceSiteInfo.Content, "default_avatar").String()
|
||||
}
|
||||
data, _ := json.Marshal(siteUsers)
|
||||
|
||||
exist, err = x.Get(&entity.SiteInfo{Type: constant.SiteTypeUsers})
|
||||
if err != nil {
|
||||
return fmt.Errorf("get config failed: %w", err)
|
||||
}
|
||||
if !exist {
|
||||
usersSiteInfo := &entity.SiteInfo{
|
||||
Type: constant.SiteTypeUsers,
|
||||
Content: string(data),
|
||||
Status: 1,
|
||||
}
|
||||
_, err = x.InsertOne(usersSiteInfo)
|
||||
if err != nil {
|
||||
return fmt.Errorf("insert site info failed: %w", err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
|
@ -110,7 +110,7 @@ ON "question" (
|
|||
{ID: 41, Key: "rank.answer.add", Value: `1`},
|
||||
{ID: 42, Key: "rank.answer.edit", Value: `200`},
|
||||
{ID: 43, Key: "rank.answer.delete", Value: `-1`},
|
||||
{ID: 44, Key: "rank.answer.accept", Value: `1`},
|
||||
{ID: 44, Key: "rank.answer.accept", Value: `-1`},
|
||||
{ID: 45, Key: "rank.answer.vote_up", Value: `15`},
|
||||
{ID: 46, Key: "rank.answer.vote_down", Value: `125`},
|
||||
{ID: 47, Key: "rank.comment.add", Value: `1`},
|
||||
|
|
|
@ -0,0 +1,118 @@
|
|||
package migrations
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/answerdev/answer/internal/entity"
|
||||
"github.com/answerdev/answer/internal/service/permission"
|
||||
"github.com/segmentfault/pacman/log"
|
||||
"xorm.io/xorm"
|
||||
)
|
||||
|
||||
func addRolePinAndHideFeatures(x *xorm.Engine) error {
|
||||
|
||||
powers := []*entity.Power{
|
||||
{ID: 34, Name: "question pin", PowerType: permission.QuestionPin, Description: "top the question"},
|
||||
{ID: 35, Name: "question hide", PowerType: permission.QuestionHide, Description: "hide the question"},
|
||||
{ID: 36, Name: "question unpin", PowerType: permission.QuestionUnPin, Description: "untop the question"},
|
||||
{ID: 37, Name: "question show", PowerType: permission.QuestionShow, Description: "show the question"},
|
||||
}
|
||||
// insert default powers
|
||||
for _, power := range powers {
|
||||
exist, err := x.Get(&entity.Power{ID: power.ID})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if exist {
|
||||
_, err = x.ID(power.ID).Update(power)
|
||||
} else {
|
||||
_, err = x.Insert(power)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
rolePowerRels := []*entity.RolePowerRel{
|
||||
|
||||
{RoleID: 2, PowerType: permission.QuestionPin},
|
||||
{RoleID: 2, PowerType: permission.QuestionHide},
|
||||
{RoleID: 2, PowerType: permission.QuestionUnPin},
|
||||
{RoleID: 2, PowerType: permission.QuestionShow},
|
||||
|
||||
{RoleID: 3, PowerType: permission.QuestionPin},
|
||||
{RoleID: 3, PowerType: permission.QuestionHide},
|
||||
{RoleID: 3, PowerType: permission.QuestionUnPin},
|
||||
{RoleID: 3, PowerType: permission.QuestionShow},
|
||||
}
|
||||
|
||||
// insert default powers
|
||||
for _, rel := range rolePowerRels {
|
||||
exist, err := x.Get(&entity.RolePowerRel{RoleID: rel.RoleID, PowerType: rel.PowerType})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if exist {
|
||||
continue
|
||||
}
|
||||
_, err = x.Insert(rel)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
defaultConfigTable := []*entity.Config{
|
||||
{ID: 119, Key: "question.pin", Value: `-1`},
|
||||
{ID: 120, Key: "question.unpin", Value: `-1`},
|
||||
{ID: 121, Key: "question.show", Value: `-1`},
|
||||
{ID: 122, Key: "question.hide", Value: `-1`},
|
||||
}
|
||||
for _, c := range defaultConfigTable {
|
||||
exist, err := x.Get(&entity.Config{ID: c.ID, Key: c.Key})
|
||||
if err != nil {
|
||||
return fmt.Errorf("get config failed: %w", err)
|
||||
}
|
||||
if exist {
|
||||
if _, err = x.Update(c, &entity.Config{ID: c.ID, Key: c.Key}); err != nil {
|
||||
log.Errorf("update %+v config failed: %s", c, err)
|
||||
return fmt.Errorf("update config failed: %w", err)
|
||||
}
|
||||
continue
|
||||
}
|
||||
if _, err = x.Insert(&entity.Config{ID: c.ID, Key: c.Key, Value: c.Value}); err != nil {
|
||||
log.Errorf("insert %+v config failed: %s", c, err)
|
||||
return fmt.Errorf("add config failed: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
type Question struct {
|
||||
ID string `xorm:"not null pk BIGINT(20) id"`
|
||||
CreatedAt time.Time `xorm:"not null default CURRENT_TIMESTAMP TIMESTAMP created_at"`
|
||||
UpdatedAt time.Time `xorm:"updated_at TIMESTAMP"`
|
||||
UserID string `xorm:"not null default 0 BIGINT(20) INDEX user_id"`
|
||||
LastEditUserID string `xorm:"not null default 0 BIGINT(20) last_edit_user_id"`
|
||||
Title string `xorm:"not null default '' VARCHAR(150) title"`
|
||||
OriginalText string `xorm:"not null MEDIUMTEXT original_text"`
|
||||
ParsedText string `xorm:"not null MEDIUMTEXT parsed_text"`
|
||||
Status int `xorm:"not null default 1 INT(11) status"`
|
||||
Pin int `xorm:"not null default 1 INT(11) pin"`
|
||||
Show int `xorm:"not null default 1 INT(11) show"`
|
||||
ViewCount int `xorm:"not null default 0 INT(11) view_count"`
|
||||
UniqueViewCount int `xorm:"not null default 0 INT(11) unique_view_count"`
|
||||
VoteCount int `xorm:"not null default 0 INT(11) vote_count"`
|
||||
AnswerCount int `xorm:"not null default 0 INT(11) answer_count"`
|
||||
CollectionCount int `xorm:"not null default 0 INT(11) collection_count"`
|
||||
FollowCount int `xorm:"not null default 0 INT(11) follow_count"`
|
||||
AcceptedAnswerID string `xorm:"not null default 0 BIGINT(20) accepted_answer_id"`
|
||||
LastAnswerID string `xorm:"not null default 0 BIGINT(20) last_answer_id"`
|
||||
PostUpdateTime time.Time `xorm:"post_update_time TIMESTAMP"`
|
||||
RevisionID string `xorm:"not null default 0 BIGINT(20) revision_id"`
|
||||
}
|
||||
err := x.Sync(new(Question))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
|
@ -1,31 +1,18 @@
|
|||
package migrations
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/answerdev/answer/internal/base/constant"
|
||||
"github.com/answerdev/answer/internal/entity"
|
||||
"github.com/answerdev/answer/internal/schema"
|
||||
"github.com/segmentfault/pacman/log"
|
||||
"xorm.io/xorm"
|
||||
)
|
||||
|
||||
func addLoginLimitations(x *xorm.Engine) error {
|
||||
loginSiteInfo := &entity.SiteInfo{
|
||||
Type: constant.SiteTypeLogin,
|
||||
}
|
||||
exist, err := x.Get(loginSiteInfo)
|
||||
if err != nil {
|
||||
return fmt.Errorf("get config failed: %w", err)
|
||||
}
|
||||
if exist {
|
||||
content := &schema.SiteLoginReq{}
|
||||
_ = json.Unmarshal([]byte(loginSiteInfo.Content), content)
|
||||
content.AllowEmailRegistrations = true
|
||||
_, err = x.ID(loginSiteInfo.ID).Cols("content").Update(loginSiteInfo)
|
||||
if err != nil {
|
||||
return fmt.Errorf("update site info failed: %w", err)
|
||||
}
|
||||
func updateAcceptAnswerRank(x *xorm.Engine) error {
|
||||
c := &entity.Config{ID: 44, Key: "rank.answer.accept", Value: `-1`}
|
||||
if _, err := x.Update(c, &entity.Config{ID: 44, Key: "rank.answer.accept"}); err != nil {
|
||||
log.Errorf("update %+v config failed: %s", c, err)
|
||||
return fmt.Errorf("update config failed: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -125,6 +125,15 @@ func (qr *questionRepo) UpdateQuestionStatusWithOutUpdateTime(ctx context.Contex
|
|||
return nil
|
||||
}
|
||||
|
||||
func (qr *questionRepo) UpdateQuestionOperation(ctx context.Context, question *entity.Question) (err error) {
|
||||
question.ID = uid.DeShortID(question.ID)
|
||||
_, err = qr.data.DB.Where("id =?", question.ID).Cols("pin", "show").Update(question)
|
||||
if err != nil {
|
||||
return errors.InternalServer(reason.DatabaseError).WithError(err).WithStack()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (qr *questionRepo) UpdateAccepted(ctx context.Context, question *entity.Question) (err error) {
|
||||
question.ID = uid.DeShortID(question.ID)
|
||||
_, err = qr.data.DB.Where("id =?", question.ID).Cols("accepted_answer_id").Update(question)
|
||||
|
@ -224,6 +233,7 @@ func (qr *questionRepo) GetQuestionIDsPage(ctx context.Context, page, pageSize i
|
|||
offset := page * pageSize
|
||||
session := qr.data.DB.Table("question")
|
||||
session = session.In("question.status", []int{entity.QuestionStatusAvailable, entity.QuestionStatusClosed})
|
||||
session.And("question.show = ?", entity.QuestionShow)
|
||||
session = session.Limit(pageSize, offset)
|
||||
session = session.OrderBy("question.created_at asc")
|
||||
err = session.Select("id,title,created_at,post_update_time").Find(&rows)
|
||||
|
@ -258,19 +268,22 @@ func (qr *questionRepo) GetQuestionPage(ctx context.Context, page, pageSize int,
|
|||
}
|
||||
if len(userID) > 0 {
|
||||
session.And("question.user_id = ?", userID)
|
||||
} else {
|
||||
session.And("question.show = ?", entity.QuestionShow)
|
||||
}
|
||||
|
||||
switch orderCond {
|
||||
case "newest":
|
||||
session.OrderBy("question.created_at DESC")
|
||||
session.OrderBy("question.pin desc,question.created_at DESC")
|
||||
case "active":
|
||||
session.OrderBy("question.post_update_time DESC, question.updated_at DESC")
|
||||
session.OrderBy("question.pin desc,question.post_update_time DESC, question.updated_at DESC")
|
||||
case "frequent":
|
||||
session.OrderBy("question.view_count DESC")
|
||||
session.OrderBy("question.pin desc,question.view_count DESC")
|
||||
case "score":
|
||||
session.OrderBy("question.vote_count DESC, question.view_count DESC")
|
||||
session.OrderBy("question.pin desc,question.vote_count DESC, question.view_count DESC")
|
||||
case "unanswered":
|
||||
session.Where("question.last_answer_id = 0")
|
||||
session.OrderBy("question.created_at DESC")
|
||||
session.OrderBy("question.pin desc,question.created_at DESC")
|
||||
}
|
||||
|
||||
total, err = pager.Help(page, pageSize, &questionList, &entity.Question{}, session)
|
||||
|
|
|
@ -94,12 +94,14 @@ func (sr *searchRepo) SearchContents(ctx context.Context, words []string, tagIDs
|
|||
ub = builder.MySQL().Select(afs...).From("`answer`").
|
||||
LeftJoin("`question`", "`question`.id = `answer`.question_id")
|
||||
|
||||
b.Where(builder.Lt{"`question`.`status`": entity.QuestionStatusDeleted})
|
||||
b.Where(builder.Lt{"`question`.`status`": entity.QuestionStatusDeleted}).
|
||||
And(builder.Eq{"`question`.`show`": entity.QuestionShow})
|
||||
ub.Where(builder.Lt{"`question`.`status`": entity.QuestionStatusDeleted}).
|
||||
And(builder.Lt{"`answer`.`status`": entity.AnswerStatusDeleted})
|
||||
And(builder.Lt{"`answer`.`status`": entity.AnswerStatusDeleted}).
|
||||
And(builder.Eq{"`question`.`show`": entity.QuestionShow})
|
||||
|
||||
argsQ = append(argsQ, entity.QuestionStatusDeleted)
|
||||
argsA = append(argsA, entity.QuestionStatusDeleted, entity.AnswerStatusDeleted)
|
||||
argsQ = append(argsQ, entity.QuestionStatusDeleted, entity.QuestionShow)
|
||||
argsA = append(argsA, entity.QuestionStatusDeleted, entity.AnswerStatusDeleted, entity.QuestionShow)
|
||||
|
||||
for i, word := range words {
|
||||
if i == 0 {
|
||||
|
@ -228,8 +230,8 @@ func (sr *searchRepo) SearchQuestions(ctx context.Context, words []string, tagID
|
|||
|
||||
b := builder.MySQL().Select(qfs...).From("question")
|
||||
|
||||
b.Where(builder.Lt{"`question`.`status`": entity.QuestionStatusDeleted})
|
||||
args = append(args, entity.QuestionStatusDeleted)
|
||||
b.Where(builder.Lt{"`question`.`status`": entity.QuestionStatusDeleted}).And(builder.Eq{"`question`.`show`": entity.QuestionShow})
|
||||
args = append(args, entity.QuestionStatusDeleted, entity.QuestionShow)
|
||||
|
||||
for i, word := range words {
|
||||
if i == 0 {
|
||||
|
@ -343,8 +345,8 @@ func (sr *searchRepo) SearchAnswers(ctx context.Context, words []string, tagIDs
|
|||
LeftJoin("`question`", "`question`.id = `answer`.question_id")
|
||||
|
||||
b.Where(builder.Lt{"`question`.`status`": entity.QuestionStatusDeleted}).
|
||||
And(builder.Lt{"`answer`.`status`": entity.AnswerStatusDeleted})
|
||||
args = append(args, entity.QuestionStatusDeleted, entity.AnswerStatusDeleted)
|
||||
And(builder.Lt{"`answer`.`status`": entity.AnswerStatusDeleted}).And(builder.Eq{"`question`.`show`": entity.QuestionShow})
|
||||
args = append(args, entity.QuestionStatusDeleted, entity.AnswerStatusDeleted, entity.QuestionShow)
|
||||
|
||||
for i, word := range words {
|
||||
if i == 0 {
|
||||
|
|
|
@ -290,8 +290,7 @@ func decorateByUserCenterUser(original *entity.User, ucUser *plugin.UserCenterBa
|
|||
original.Mobile = ucUser.Mobile
|
||||
}
|
||||
if len(ucUser.Bio) > 0 {
|
||||
original.Bio = ucUser.Bio
|
||||
original.BioHTML = converter.Markdown2HTML(ucUser.Bio)
|
||||
original.BioHTML = converter.Markdown2HTML(ucUser.Bio) + original.BioHTML
|
||||
}
|
||||
|
||||
// If plugin enable rank agent, use rank from user center.
|
||||
|
|
|
@ -102,8 +102,8 @@ func (a *AnswerAPIRouter) RegisterMustUnAuthAnswerAPIRouter(r *gin.RouterGroup)
|
|||
|
||||
// user
|
||||
r.GET("/user/info", a.userController.GetUserInfoByUserID)
|
||||
routerGroup := r.Group("", middleware.BanAPIWhenUserCenterEnabled)
|
||||
r.POST("/user/login/email", a.userController.UserEmailLogin)
|
||||
routerGroup := r.Group("", middleware.BanAPIForUserCenter)
|
||||
routerGroup.POST("/user/login/email", a.userController.UserEmailLogin)
|
||||
routerGroup.POST("/user/register/email", a.userController.UserRegisterByEmail)
|
||||
routerGroup.GET("/user/register/captcha", a.userController.UserRegisterCaptcha)
|
||||
routerGroup.POST("/user/email/verification", a.userController.UserVerifyEmail)
|
||||
|
@ -117,8 +117,8 @@ func (a *AnswerAPIRouter) RegisterMustUnAuthAnswerAPIRouter(r *gin.RouterGroup)
|
|||
func (a *AnswerAPIRouter) RegisterUnAuthAnswerAPIRouter(r *gin.RouterGroup) {
|
||||
// user
|
||||
r.GET("/user/logout", a.userController.UserLogout)
|
||||
r.POST("/user/email/change/code", middleware.BanAPIWhenUserCenterEnabled, a.userController.UserChangeEmailSendCode)
|
||||
r.POST("/user/email/verification/send", middleware.BanAPIWhenUserCenterEnabled, a.userController.UserVerifyEmailSend)
|
||||
r.POST("/user/email/change/code", middleware.BanAPIForUserCenter, a.userController.UserChangeEmailSendCode)
|
||||
r.POST("/user/email/verification/send", middleware.BanAPIForUserCenter, a.userController.UserVerifyEmailSend)
|
||||
r.GET("/personal/user/info", a.userController.GetOtherUserInfoByUsername)
|
||||
r.GET("/user/ranking", a.userController.UserRanking)
|
||||
|
||||
|
@ -195,6 +195,7 @@ func (a *AnswerAPIRouter) RegisterAnswerAPIRouter(r *gin.RouterGroup) {
|
|||
r.PUT("/question", a.questionController.UpdateQuestion)
|
||||
r.DELETE("/question", a.questionController.RemoveQuestion)
|
||||
r.PUT("/question/status", a.questionController.CloseQuestion)
|
||||
r.PUT("/question/operation", a.questionController.OperationQuestion)
|
||||
r.PUT("/question/reopen", a.questionController.ReopenQuestion)
|
||||
r.GET("/question/similar", a.questionController.SearchByTitleLike)
|
||||
|
||||
|
@ -205,8 +206,8 @@ func (a *AnswerAPIRouter) RegisterAnswerAPIRouter(r *gin.RouterGroup) {
|
|||
r.DELETE("/answer", a.answerController.RemoveAnswer)
|
||||
|
||||
// user
|
||||
r.PUT("/user/password", middleware.BanAPIWhenUserCenterEnabled, a.userController.UserModifyPassWord)
|
||||
r.PUT("/user/info", middleware.BanAPIWhenUserCenterEnabled, a.userController.UserUpdateInfo)
|
||||
r.PUT("/user/password", middleware.BanAPIForUserCenter, a.userController.UserModifyPassWord)
|
||||
r.PUT("/user/info", a.userController.UserUpdateInfo)
|
||||
r.PUT("/user/interface", a.userController.UserUpdateInterface)
|
||||
r.POST("/user/notice/set", a.userController.UserNoticeSet)
|
||||
|
||||
|
@ -247,8 +248,8 @@ func (a *AnswerAPIRouter) RegisterAnswerAdminAPIRouter(r *gin.RouterGroup) {
|
|||
r.GET("/users/page", a.adminUserController.GetUserPage)
|
||||
r.PUT("/user/status", a.adminUserController.UpdateUserStatus)
|
||||
r.PUT("/user/role", a.adminUserController.UpdateUserRole)
|
||||
r.POST("/user", middleware.BanAPIWhenUserCenterEnabled, a.adminUserController.AddUser)
|
||||
r.PUT("/user/password", middleware.BanAPIWhenUserCenterEnabled, a.adminUserController.UpdateUserPassword)
|
||||
r.POST("/user", middleware.BanAPIForUserCenter, a.adminUserController.AddUser)
|
||||
r.PUT("/user/password", middleware.BanAPIForUserCenter, a.adminUserController.UpdateUserPassword)
|
||||
|
||||
// reason
|
||||
r.GET("/reasons", a.reasonController.Reasons)
|
||||
|
|
|
@ -56,10 +56,17 @@ func (g *GetPluginConfigResp) SetConfigFields(ctx *gin.Context, fields []plugin.
|
|||
UIOptions: ConfigFieldUIOptions{
|
||||
Rows: field.UIOptions.Rows,
|
||||
InputType: string(field.UIOptions.InputType),
|
||||
Variant: field.UIOptions.Variant,
|
||||
},
|
||||
}
|
||||
configField.UIOptions.Placeholder = field.UIOptions.Placeholder.Translate(ctx)
|
||||
configField.UIOptions.Label = field.UIOptions.Label.Translate(ctx)
|
||||
configField.UIOptions.Text = field.UIOptions.Text.Translate(ctx)
|
||||
if field.UIOptions.Action != nil {
|
||||
configField.UIOptions.Action = &ConfigFieldUIOptionAction{
|
||||
Url: field.UIOptions.Action.Url,
|
||||
}
|
||||
}
|
||||
|
||||
for _, option := range field.Options {
|
||||
configField.Options = append(configField.Options, ConfigFieldOption{
|
||||
|
@ -83,10 +90,13 @@ type ConfigField struct {
|
|||
}
|
||||
|
||||
type ConfigFieldUIOptions struct {
|
||||
Placeholder string `json:"placeholder,omitempty"`
|
||||
Rows string `json:"rows,omitempty"`
|
||||
InputType string `json:"input_type,omitempty"`
|
||||
Label string `json:"label,omitempty"`
|
||||
Placeholder string `json:"placeholder,omitempty"`
|
||||
Rows string `json:"rows,omitempty"`
|
||||
InputType string `json:"input_type,omitempty"`
|
||||
Label string `json:"label,omitempty"`
|
||||
Action *ConfigFieldUIOptionAction `json:"action,omitempty"`
|
||||
Variant string `json:"variant,omitempty"`
|
||||
Text string `json:"text,omitempty"`
|
||||
}
|
||||
|
||||
type ConfigFieldOption struct {
|
||||
|
@ -94,6 +104,10 @@ type ConfigFieldOption struct {
|
|||
Value string `json:"value"`
|
||||
}
|
||||
|
||||
type ConfigFieldUIOptionAction struct {
|
||||
Url string `json:"url"`
|
||||
}
|
||||
|
||||
type UpdatePluginConfigReq struct {
|
||||
PluginSlugName string `validate:"required,gt=1,lte=100" json:"plugin_slug_name"`
|
||||
ConfigFields map[string]any `json:"config_fields"`
|
||||
|
|
|
@ -6,12 +6,14 @@ type UserCenterAgentResp struct {
|
|||
}
|
||||
|
||||
type AgentInfo struct {
|
||||
Name string `json:"name"`
|
||||
Icon string `json:"icon"`
|
||||
Url string `json:"url"`
|
||||
LoginRedirectURL string `json:"login_redirect_url"`
|
||||
SignUpRedirectURL string `json:"sign_up_redirect_url"`
|
||||
ControlCenterItems []*ControlCenter `json:"control_center"`
|
||||
Name string `json:"name"`
|
||||
DisplayName string `json:"display_name"`
|
||||
Icon string `json:"icon"`
|
||||
Url string `json:"url"`
|
||||
LoginRedirectURL string `json:"login_redirect_url"`
|
||||
SignUpRedirectURL string `json:"sign_up_redirect_url"`
|
||||
ControlCenterItems []*ControlCenter `json:"control_center"`
|
||||
EnabledOriginalUserSystem bool `json:"enabled_original_user_system"`
|
||||
}
|
||||
|
||||
type ControlCenter struct {
|
||||
|
|
|
@ -8,9 +8,13 @@ import (
|
|||
)
|
||||
|
||||
const (
|
||||
SitemapMaxSize = 50000
|
||||
SitemapCachekey = "answer@sitemap"
|
||||
SitemapPageCachekey = "answer@sitemap@page%d"
|
||||
SitemapMaxSize = 50000
|
||||
SitemapCachekey = "answer@sitemap"
|
||||
SitemapPageCachekey = "answer@sitemap@page%d"
|
||||
QuestionOperationPin = "pin"
|
||||
QuestionOperationUnPin = "unpin"
|
||||
QuestionOperationHide = "hide"
|
||||
QuestionOperationShow = "show"
|
||||
)
|
||||
|
||||
// RemoveQuestionReq delete question request
|
||||
|
@ -28,6 +32,14 @@ type CloseQuestionReq struct {
|
|||
UserID string `json:"-"` // user_id
|
||||
}
|
||||
|
||||
type OperationQuestionReq struct {
|
||||
ID string `validate:"required" json:"id"`
|
||||
Operation string `json:"operation"` // operation [pin unpin hide show]
|
||||
UserID string `json:"-"` // user_id
|
||||
CanPin bool `json:"-"`
|
||||
CanList bool `json:"-"`
|
||||
}
|
||||
|
||||
type CloseQuestionMeta struct {
|
||||
CloseType int `json:"close_type"`
|
||||
CloseMsg string `json:"close_msg"`
|
||||
|
@ -101,6 +113,12 @@ type QuestionPermission struct {
|
|||
CanClose bool `json:"-"`
|
||||
// whether user can reopen it
|
||||
CanReopen bool `json:"-"`
|
||||
// whether user can pin it
|
||||
CanPin bool `json:"-"`
|
||||
CanUnPin bool `json:"-"`
|
||||
// whether user can hide it
|
||||
CanHide bool `json:"-"`
|
||||
CanShow bool `json:"-"`
|
||||
// whether user can use reserved it
|
||||
CanUseReservedTag bool `json:"-"`
|
||||
}
|
||||
|
@ -168,6 +186,8 @@ type QuestionInfo struct {
|
|||
UpdateTime int64 `json:"-"` // update_time
|
||||
PostUpdateTime int64 `json:"update_time"`
|
||||
QuestionUpdateTime int64 `json:"edit_time"`
|
||||
Pin int `json:"pin"` // 1: unpin, 2: pin
|
||||
Show int `json:"show"` // 0: show, 1: hide
|
||||
Status int `json:"status"`
|
||||
Operation *Operation `json:"operation,omitempty"`
|
||||
UserID string `json:"-" `
|
||||
|
@ -296,6 +316,8 @@ type QuestionPageResp struct {
|
|||
Title string `json:"title"`
|
||||
UrlTitle string `json:"url_title"`
|
||||
Description string `json:"description"`
|
||||
Pin int `json:"pin"` // 1: unpin, 2: pin
|
||||
Show int `json:"show"` // 0: show, 1: hide
|
||||
Status int `json:"status"`
|
||||
Tags []*TagResp `json:"tags"`
|
||||
|
||||
|
|
|
@ -43,9 +43,8 @@ func (r *SiteGeneralReq) FormatSiteUrl() {
|
|||
|
||||
// SiteInterfaceReq site interface request
|
||||
type SiteInterfaceReq struct {
|
||||
Language string `validate:"required,gt=1,lte=128" form:"language" json:"language"`
|
||||
TimeZone string `validate:"required,gt=1,lte=128" form:"time_zone" json:"time_zone"`
|
||||
DefaultAvatar string `validate:"required,oneof=system gravatar" form:"default_avatar" json:"default_avatar"`
|
||||
Language string `validate:"required,gt=1,lte=128" form:"language" json:"language"`
|
||||
TimeZone string `validate:"required,gt=1,lte=128" form:"time_zone" json:"time_zone"`
|
||||
}
|
||||
|
||||
// SiteBrandingReq site branding request
|
||||
|
@ -275,6 +274,7 @@ type GetPrivilegesConfigResp struct {
|
|||
// PrivilegeOption privilege option
|
||||
type PrivilegeOption struct {
|
||||
Level PrivilegeLevel `json:"level"`
|
||||
LevelDesc string `json:"level_desc"`
|
||||
Privileges []*constant.Privilege `json:"privileges"`
|
||||
}
|
||||
|
||||
|
@ -289,7 +289,6 @@ var (
|
|||
constant.RankQuestionAddKey: {1, 1, 1},
|
||||
constant.RankAnswerAddKey: {1, 1, 1},
|
||||
constant.RankCommentAddKey: {1, 1, 1},
|
||||
constant.RankAnswerAcceptKey: {1, 1, 1},
|
||||
constant.RankReportAddKey: {1, 1, 1},
|
||||
constant.RankCommentVoteUpKey: {1, 1, 1},
|
||||
constant.RankLinkUrlLimitKey: {1, 10, 10},
|
||||
|
@ -312,21 +311,27 @@ var (
|
|||
)
|
||||
|
||||
func init() {
|
||||
for _, option := range []PrivilegeLevel{PrivilegeLevel1, PrivilegeLevel2, PrivilegeLevel3} {
|
||||
op := &PrivilegeOption{
|
||||
Level: option,
|
||||
}
|
||||
DefaultPrivilegeOptions = append(DefaultPrivilegeOptions, &PrivilegeOption{
|
||||
Level: PrivilegeLevel1,
|
||||
LevelDesc: reason.PrivilegeLevel1Desc,
|
||||
}, &PrivilegeOption{
|
||||
Level: PrivilegeLevel2,
|
||||
LevelDesc: reason.PrivilegeLevel2Desc,
|
||||
}, &PrivilegeOption{
|
||||
Level: PrivilegeLevel3,
|
||||
LevelDesc: reason.PrivilegeLevel3Desc,
|
||||
})
|
||||
|
||||
for _, option := range DefaultPrivilegeOptions {
|
||||
for _, privilege := range constant.RankAllPrivileges {
|
||||
if len(privilegeOptionsLevelMapping[privilege.Key]) == 0 {
|
||||
fmt.Println("privilege key not found: ", privilege.Key)
|
||||
continue
|
||||
}
|
||||
op.Privileges = append(op.Privileges, &constant.Privilege{
|
||||
option.Privileges = append(option.Privileges, &constant.Privilege{
|
||||
Label: privilege.Label,
|
||||
Value: privilegeOptionsLevelMapping[privilege.Key][option-1],
|
||||
Value: privilegeOptionsLevelMapping[privilege.Key][option.Level-1],
|
||||
Key: privilege.Key,
|
||||
})
|
||||
}
|
||||
DefaultPrivilegeOptions = append(DefaultPrivilegeOptions, op)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,7 +5,8 @@ type UserExternalLoginResp struct {
|
|||
BindingKey string `json:"binding_key"`
|
||||
AccessToken string `json:"access_token"`
|
||||
// ErrMsg error message, if not empty, means login failed and this message should be displayed.
|
||||
ErrMsg string `json:"-"`
|
||||
ErrMsg string `json:"-"`
|
||||
ErrTitle string `json:"-"`
|
||||
}
|
||||
|
||||
// ExternalLoginBindingUserSendEmailReq external login binding user request
|
||||
|
@ -68,7 +69,8 @@ type UserCenterUserSettingsResp struct {
|
|||
}
|
||||
|
||||
type UserCenterAdminFunctionAgentResp struct {
|
||||
RoleAgentEnabled bool `json:"role_agent_enabled"`
|
||||
UserStatusAgentEnabled bool `json:"user_status_agent_enabled"`
|
||||
UserPasswordAgentEnabled bool `json:"user_password_agent_enabled"`
|
||||
}
|
||||
|
||||
type UserSettingAgent struct {
|
||||
|
|
|
@ -5,6 +5,7 @@ import (
|
|||
|
||||
"github.com/answerdev/answer/internal/entity"
|
||||
"github.com/answerdev/answer/pkg/token"
|
||||
"github.com/answerdev/answer/plugin"
|
||||
"github.com/segmentfault/pacman/log"
|
||||
)
|
||||
|
||||
|
@ -52,6 +53,14 @@ func (as *AuthService) GetUserCacheInfo(ctx context.Context, accessToken string)
|
|||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// try to get user status from user center
|
||||
uc, ok := plugin.GetUserCenter()
|
||||
if ok && len(userCacheInfo.ExternalID) > 0 {
|
||||
if userStatus := uc.UserStatus(userCacheInfo.ExternalID); userStatus != plugin.UserStatusAvailable {
|
||||
userCacheInfo.UserStatus = int(userStatus)
|
||||
}
|
||||
}
|
||||
return userCacheInfo, nil
|
||||
}
|
||||
|
||||
|
|
|
@ -10,6 +10,10 @@ const (
|
|||
QuestionReopen = "question.reopen"
|
||||
QuestionVoteUp = "question.vote_up"
|
||||
QuestionVoteDown = "question.vote_down"
|
||||
QuestionPin = "question.pin" //Top the question
|
||||
QuestionUnPin = "question.unpin" //untop the question
|
||||
QuestionHide = "question.hide" //hide the question
|
||||
QuestionShow = "question.show" //show the question
|
||||
AnswerAdd = "answer.add"
|
||||
AnswerEdit = "answer.edit"
|
||||
AnswerEditWithoutReview = "answer.edit_without_review"
|
||||
|
@ -43,4 +47,8 @@ const (
|
|||
deleteActionName = "action.delete"
|
||||
closeActionName = "action.close"
|
||||
reopenActionName = "action.reopen"
|
||||
pinActionName = "action.pin"
|
||||
unpinActionName = "action.unpin"
|
||||
hideActionName = "action.hide"
|
||||
showActionName = "action.show"
|
||||
)
|
||||
|
|
|
@ -10,7 +10,7 @@ import (
|
|||
|
||||
// GetQuestionPermission get question permission
|
||||
func GetQuestionPermission(ctx context.Context, userID string, creatorUserID string,
|
||||
canEdit, canDelete, canClose, canReopen bool) (
|
||||
canEdit, canDelete, canClose, canReopen, canPin, canHide, CanUnPin, canShow bool) (
|
||||
actions []*schema.PermissionMemberAction) {
|
||||
lang := handler.GetLangByCtx(ctx)
|
||||
actions = make([]*schema.PermissionMemberAction, 0)
|
||||
|
@ -42,6 +42,36 @@ func GetQuestionPermission(ctx context.Context, userID string, creatorUserID str
|
|||
Type: "confirm",
|
||||
})
|
||||
}
|
||||
if canPin {
|
||||
actions = append(actions, &schema.PermissionMemberAction{
|
||||
Action: "pin",
|
||||
Name: translator.Tr(lang, pinActionName),
|
||||
Type: "confirm",
|
||||
})
|
||||
}
|
||||
if canHide {
|
||||
actions = append(actions, &schema.PermissionMemberAction{
|
||||
Action: "hide",
|
||||
Name: translator.Tr(lang, hideActionName),
|
||||
Type: "confirm",
|
||||
})
|
||||
}
|
||||
|
||||
if CanUnPin {
|
||||
actions = append(actions, &schema.PermissionMemberAction{
|
||||
Action: "unpin",
|
||||
Name: translator.Tr(lang, unpinActionName),
|
||||
Type: "confirm",
|
||||
})
|
||||
}
|
||||
|
||||
if canShow {
|
||||
actions = append(actions, &schema.PermissionMemberAction{
|
||||
Action: "show",
|
||||
Name: translator.Tr(lang, showActionName),
|
||||
Type: "confirm",
|
||||
})
|
||||
}
|
||||
if canDelete || userID == creatorUserID {
|
||||
actions = append(actions, &schema.PermissionMemberAction{
|
||||
Action: "delete",
|
||||
|
|
|
@ -36,6 +36,7 @@ type QuestionRepo interface {
|
|||
questionList []*entity.Question, total int64, err error)
|
||||
UpdateQuestionStatus(ctx context.Context, question *entity.Question) (err error)
|
||||
UpdateQuestionStatusWithOutUpdateTime(ctx context.Context, question *entity.Question) (err error)
|
||||
UpdateQuestionOperation(ctx context.Context, question *entity.Question) (err error)
|
||||
SearchByTitleLike(ctx context.Context, title string) (questionList []*entity.Question, err error)
|
||||
UpdatePvCount(ctx context.Context, questionID string) (err error)
|
||||
UpdateAnswerCount(ctx context.Context, questionID string, num int) (err error)
|
||||
|
@ -271,6 +272,8 @@ func (qs *QuestionCommon) FormatQuestionsPage(
|
|||
FollowCount: questionInfo.FollowCount,
|
||||
AcceptedAnswerID: questionInfo.AcceptedAnswerID,
|
||||
LastAnswerID: questionInfo.LastAnswerID,
|
||||
Pin: questionInfo.Pin,
|
||||
Show: questionInfo.Show,
|
||||
}
|
||||
|
||||
questionIDs = append(questionIDs, questionInfo.ID)
|
||||
|
@ -526,6 +529,8 @@ func (qs *QuestionCommon) ShowFormat(ctx context.Context, data *entity.Question)
|
|||
info.QuestionUpdateTime = 0
|
||||
}
|
||||
info.Status = data.Status
|
||||
info.Pin = data.Pin
|
||||
info.Show = data.Show
|
||||
info.UserID = data.UserID
|
||||
info.LastEditUserID = data.LastEditUserID
|
||||
if data.LastAnswerID != "0" {
|
||||
|
|
|
@ -270,6 +270,8 @@ func (qs *QuestionService) AddQuestion(ctx context.Context, req *schema.Question
|
|||
question.Status = entity.QuestionStatusAvailable
|
||||
question.RevisionID = "0"
|
||||
question.CreatedAt = now
|
||||
question.Pin = entity.QuestionUnPin
|
||||
question.Show = entity.QuestionShow
|
||||
//question.UpdatedAt = nil
|
||||
err = qs.questionRepo.AddQuestion(ctx, question)
|
||||
if err != nil {
|
||||
|
@ -319,6 +321,58 @@ func (qs *QuestionService) AddQuestion(ctx context.Context, req *schema.Question
|
|||
return
|
||||
}
|
||||
|
||||
// OperationQuestion
|
||||
func (qs *QuestionService) OperationQuestion(ctx context.Context, req *schema.OperationQuestionReq) (err error) {
|
||||
questionInfo, has, err := qs.questionRepo.GetQuestion(ctx, req.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !has {
|
||||
return nil
|
||||
}
|
||||
// Hidden question cannot be placed at the top
|
||||
if questionInfo.Show == entity.QuestionHide && req.Operation == schema.QuestionOperationPin {
|
||||
return nil
|
||||
}
|
||||
// Question cannot be hidden when they are at the top
|
||||
if questionInfo.Pin == entity.QuestionPin && req.Operation == schema.QuestionOperationHide {
|
||||
return nil
|
||||
}
|
||||
|
||||
switch req.Operation {
|
||||
case schema.QuestionOperationHide:
|
||||
questionInfo.Show = entity.QuestionHide
|
||||
case schema.QuestionOperationShow:
|
||||
questionInfo.Show = entity.QuestionShow
|
||||
case schema.QuestionOperationPin:
|
||||
questionInfo.Pin = entity.QuestionPin
|
||||
case schema.QuestionOperationUnPin:
|
||||
questionInfo.Pin = entity.QuestionUnPin
|
||||
}
|
||||
|
||||
err = qs.questionRepo.UpdateQuestionOperation(ctx, questionInfo)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
actMap := make(map[string]constant.ActivityTypeKey)
|
||||
actMap[schema.QuestionOperationPin] = constant.ActQuestionPin
|
||||
actMap[schema.QuestionOperationUnPin] = constant.ActQuestionUnPin
|
||||
actMap[schema.QuestionOperationHide] = constant.ActQuestionHide
|
||||
actMap[schema.QuestionOperationShow] = constant.ActQuestionShow
|
||||
_, ok := actMap[req.Operation]
|
||||
if ok {
|
||||
activity_queue.AddActivity(&schema.ActivityMsg{
|
||||
UserID: req.UserID,
|
||||
ObjectID: questionInfo.ID,
|
||||
OriginalObjectID: questionInfo.ID,
|
||||
ActivityTypeKey: actMap[req.Operation],
|
||||
})
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// RemoveQuestion delete question
|
||||
func (qs *QuestionService) RemoveQuestion(ctx context.Context, req *schema.RemoveQuestionReq) (err error) {
|
||||
questionInfo, has, err := qs.questionRepo.GetQuestion(ctx, req.ID)
|
||||
|
@ -632,6 +686,21 @@ func (qs *QuestionService) GetQuestion(ctx context.Context, questionID, userID s
|
|||
if question.Status == entity.QuestionStatusClosed {
|
||||
per.CanClose = false
|
||||
}
|
||||
if question.Pin == entity.QuestionPin {
|
||||
per.CanPin = false
|
||||
per.CanHide = false
|
||||
}
|
||||
if question.Pin == entity.QuestionUnPin {
|
||||
per.CanUnPin = false
|
||||
}
|
||||
if question.Show == entity.QuestionShow {
|
||||
per.CanShow = false
|
||||
}
|
||||
if question.Show == entity.QuestionHide {
|
||||
per.CanHide = false
|
||||
per.CanPin = false
|
||||
}
|
||||
|
||||
if question.Status == entity.QuestionStatusDeleted {
|
||||
operation := &schema.Operation{}
|
||||
operation.Msg = translator.Tr(handler.GetLangByCtx(ctx), reason.QuestionAlreadyDeleted)
|
||||
|
@ -641,7 +710,7 @@ func (qs *QuestionService) GetQuestion(ctx context.Context, questionID, userID s
|
|||
|
||||
question.Description = htmltext.FetchExcerpt(question.HTML, "...", 240)
|
||||
question.MemberActions = permission.GetQuestionPermission(ctx, userID, question.UserID,
|
||||
per.CanEdit, per.CanDelete, per.CanClose, per.CanReopen)
|
||||
per.CanEdit, per.CanDelete, per.CanClose, per.CanReopen, per.CanPin, per.CanHide, per.CanUnPin, per.CanShow)
|
||||
return question, nil
|
||||
}
|
||||
|
||||
|
@ -735,14 +804,15 @@ func (qs *QuestionService) SearchUserAnswerList(ctx context.Context, userName, o
|
|||
if ok {
|
||||
item.QuestionInfo = questionMaps[item.QuestionID]
|
||||
}
|
||||
}
|
||||
for _, item := range answerlist {
|
||||
info := &schema.UserAnswerInfo{}
|
||||
_ = copier.Copy(info, item)
|
||||
info.AnswerID = item.ID
|
||||
info.QuestionID = item.QuestionID
|
||||
userAnswerlist = append(userAnswerlist, info)
|
||||
if item.QuestionInfo.Status != entity.QuestionStatusDeleted {
|
||||
userAnswerlist = append(userAnswerlist, info)
|
||||
}
|
||||
}
|
||||
|
||||
return userAnswerlist, count, nil
|
||||
}
|
||||
|
||||
|
|
|
@ -6,6 +6,7 @@ import (
|
|||
"fmt"
|
||||
|
||||
"github.com/answerdev/answer/internal/base/constant"
|
||||
"github.com/answerdev/answer/internal/base/handler"
|
||||
"github.com/answerdev/answer/internal/base/reason"
|
||||
"github.com/answerdev/answer/internal/base/translator"
|
||||
"github.com/answerdev/answer/internal/entity"
|
||||
|
@ -35,7 +36,7 @@ func NewSiteInfoService(
|
|||
tagCommonService *tagcommon.TagCommonService,
|
||||
configRepo config.ConfigRepo,
|
||||
) *SiteInfoService {
|
||||
resp, err := siteInfoCommonService.GetSiteInterface(context.Background())
|
||||
resp, err := siteInfoCommonService.GetSiteUsers(context.Background())
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
} else {
|
||||
|
@ -131,29 +132,18 @@ func (s *SiteInfoService) SaveSiteGeneral(ctx context.Context, req schema.SiteGe
|
|||
}
|
||||
|
||||
func (s *SiteInfoService) SaveSiteInterface(ctx context.Context, req schema.SiteInterfaceReq) (err error) {
|
||||
var (
|
||||
siteType = "interface"
|
||||
content []byte
|
||||
)
|
||||
|
||||
// check language
|
||||
if !translator.CheckLanguageIsValid(req.Language) {
|
||||
err = errors.BadRequest(reason.LangNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
content, _ = json.Marshal(req)
|
||||
|
||||
content, _ := json.Marshal(req)
|
||||
data := entity.SiteInfo{
|
||||
Type: siteType,
|
||||
Type: constant.SiteTypeInterface,
|
||||
Content: string(content),
|
||||
}
|
||||
|
||||
err = s.siteInfoRepo.SaveByType(ctx, siteType, &data)
|
||||
if err == nil {
|
||||
constant.DefaultAvatar = req.DefaultAvatar
|
||||
}
|
||||
return
|
||||
return s.siteInfoRepo.SaveByType(ctx, constant.SiteTypeInterface, &data)
|
||||
}
|
||||
|
||||
// SaveSiteBranding save site branding information
|
||||
|
@ -235,7 +225,11 @@ func (s *SiteInfoService) SaveSiteUsers(ctx context.Context, req *schema.SiteUse
|
|||
Content: string(content),
|
||||
Status: 1,
|
||||
}
|
||||
return s.siteInfoRepo.SaveByType(ctx, constant.SiteTypeUsers, data)
|
||||
err = s.siteInfoRepo.SaveByType(ctx, constant.SiteTypeUsers, data)
|
||||
if err == nil {
|
||||
constant.DefaultAvatar = req.DefaultAvatar
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// GetSMTPConfig get smtp config
|
||||
|
@ -329,8 +323,8 @@ func (s *SiteInfoService) GetPrivilegesConfig(ctx context.Context) (resp *schema
|
|||
return nil, err
|
||||
}
|
||||
resp = &schema.GetPrivilegesConfigResp{
|
||||
Options: schema.DefaultPrivilegeOptions,
|
||||
SelectedLevel: schema.PrivilegeLevel2,
|
||||
Options: s.translatePrivilegeOptions(ctx),
|
||||
SelectedLevel: schema.PrivilegeLevel3,
|
||||
}
|
||||
if privilege != nil && privilege.Level > 0 {
|
||||
resp.SelectedLevel = privilege.Level
|
||||
|
@ -338,6 +332,25 @@ func (s *SiteInfoService) GetPrivilegesConfig(ctx context.Context) (resp *schema
|
|||
return resp, nil
|
||||
}
|
||||
|
||||
func (s *SiteInfoService) translatePrivilegeOptions(ctx context.Context) (options []*schema.PrivilegeOption) {
|
||||
la := handler.GetLangByCtx(ctx)
|
||||
for _, option := range schema.DefaultPrivilegeOptions {
|
||||
op := &schema.PrivilegeOption{
|
||||
Level: option.Level,
|
||||
LevelDesc: translator.Tr(la, option.LevelDesc),
|
||||
}
|
||||
for _, privilege := range option.Privileges {
|
||||
op.Privileges = append(op.Privileges, &constant.Privilege{
|
||||
Key: privilege.Key,
|
||||
Label: translator.Tr(la, privilege.Label),
|
||||
Value: privilege.Value,
|
||||
})
|
||||
}
|
||||
options = append(options, op)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (s *SiteInfoService) UpdatePrivilegesConfig(ctx context.Context, req *schema.UpdatePrivilegesConfigReq) (err error) {
|
||||
var chooseOption *schema.PrivilegeOption
|
||||
for _, option := range schema.DefaultPrivilegeOptions {
|
||||
|
|
|
@ -150,7 +150,7 @@ func (us *UserCommon) MakeUsername(ctx context.Context, displayName string) (use
|
|||
return username + suffix, nil
|
||||
}
|
||||
|
||||
func (us *UserCommon) CacheLoginUserInfo(ctx context.Context, userID string, userStatus, emailStatus int) (
|
||||
func (us *UserCommon) CacheLoginUserInfo(ctx context.Context, userID string, userStatus, emailStatus int, externalID string) (
|
||||
accessToken string, userCacheInfo *entity.UserCacheInfo, err error) {
|
||||
roleID, err := us.userRoleService.GetUserRole(ctx, userID)
|
||||
if err != nil {
|
||||
|
@ -162,6 +162,7 @@ func (us *UserCommon) CacheLoginUserInfo(ctx context.Context, userID string, use
|
|||
EmailStatus: emailStatus,
|
||||
UserStatus: userStatus,
|
||||
RoleID: roleID,
|
||||
ExternalID: externalID,
|
||||
}
|
||||
|
||||
accessToken, err = us.authService.SetUserCacheInfo(ctx, userCacheInfo)
|
||||
|
|
|
@ -14,6 +14,7 @@ import (
|
|||
"github.com/answerdev/answer/internal/service/siteinfo_common"
|
||||
usercommon "github.com/answerdev/answer/internal/service/user_common"
|
||||
"github.com/answerdev/answer/pkg/checker"
|
||||
"github.com/answerdev/answer/pkg/converter"
|
||||
"github.com/answerdev/answer/pkg/random"
|
||||
"github.com/answerdev/answer/plugin"
|
||||
"github.com/segmentfault/pacman/log"
|
||||
|
@ -58,7 +59,9 @@ func (us *UserCenterLoginService) ExternalLogin(
|
|||
if !checker.EmailInAllowEmailDomain(basicUserInfo.Email, siteInfo.AllowEmailDomains) {
|
||||
log.Debugf("email domain not allowed: %s", basicUserInfo.Email)
|
||||
return &schema.UserExternalLoginResp{
|
||||
ErrMsg: translator.Tr(handler.GetLangByCtx(ctx), reason.EmailIllegalDomainError)}, nil
|
||||
ErrTitle: translator.Tr(handler.GetLangByCtx(ctx), reason.UserAccessDenied),
|
||||
ErrMsg: translator.Tr(handler.GetLangByCtx(ctx), reason.EmailIllegalDomainError),
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -74,18 +77,24 @@ func (us *UserCenterLoginService) ExternalLogin(
|
|||
return nil, err
|
||||
}
|
||||
if exist {
|
||||
// if user is deleted, do not allow login
|
||||
if oldUserInfo.Status == entity.UserStatusDeleted {
|
||||
return &schema.UserExternalLoginResp{
|
||||
ErrTitle: translator.Tr(handler.GetLangByCtx(ctx), reason.UserAccessDenied),
|
||||
ErrMsg: translator.Tr(handler.GetLangByCtx(ctx), reason.UserPageAccessDenied),
|
||||
}, nil
|
||||
}
|
||||
if err := us.userRepo.UpdateLastLoginDate(ctx, oldUserInfo.ID); err != nil {
|
||||
log.Errorf("update user last login date failed: %v", err)
|
||||
}
|
||||
accessToken, _, err := us.userCommonService.CacheLoginUserInfo(
|
||||
ctx, oldUserInfo.ID, oldUserInfo.MailStatus, oldUserInfo.Status)
|
||||
ctx, oldUserInfo.ID, oldUserInfo.MailStatus, oldUserInfo.Status, oldExternalLoginUserInfo.ExternalID)
|
||||
return &schema.UserExternalLoginResp{AccessToken: accessToken}, err
|
||||
}
|
||||
}
|
||||
|
||||
// cache external user info, waiting for user enter email address.
|
||||
if userCenter.Description().MustAuthEmailEnabled && len(basicUserInfo.Email) == 0 {
|
||||
// TODO: check
|
||||
return &schema.UserExternalLoginResp{ErrMsg: "Requires authorized email to login"}, nil
|
||||
}
|
||||
|
||||
|
@ -97,7 +106,7 @@ func (us *UserCenterLoginService) ExternalLogin(
|
|||
us.activeUser(ctx, oldUserInfo)
|
||||
|
||||
accessToken, _, err := us.userCommonService.CacheLoginUserInfo(
|
||||
ctx, oldUserInfo.ID, oldUserInfo.MailStatus, oldUserInfo.Status)
|
||||
ctx, oldUserInfo.ID, oldUserInfo.MailStatus, oldUserInfo.Status, oldExternalLoginUserInfo.ExternalID)
|
||||
return &schema.UserExternalLoginResp{AccessToken: accessToken}, err
|
||||
}
|
||||
|
||||
|
@ -126,7 +135,7 @@ func (us *UserCenterLoginService) registerNewUser(ctx context.Context, provider
|
|||
userInfo.Status = entity.UserStatusAvailable
|
||||
userInfo.LastLoginDate = time.Now()
|
||||
userInfo.Bio = basicUserInfo.Bio
|
||||
userInfo.BioHTML = basicUserInfo.Bio
|
||||
userInfo.BioHTML = converter.Markdown2HTML(basicUserInfo.Bio)
|
||||
err = us.userRepo.AddUser(ctx, userInfo)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -203,7 +212,11 @@ func (us *UserCenterLoginService) UserCenterAdminFunctionAgent(ctx context.Conte
|
|||
return
|
||||
}
|
||||
desc := userCenter.Description()
|
||||
resp.RoleAgentEnabled = desc.RoleAgentEnabled
|
||||
// If user status agent is enabled, admin can not update user status in answer.
|
||||
resp.UserStatusAgentEnabled = desc.UserStatusAgentEnabled
|
||||
// If original user system is enabled, admin can update user password in answer.
|
||||
// So user password agent is disabled.
|
||||
resp.UserPasswordAgentEnabled = !desc.EnabledOriginalUserSystem
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
|
|
|
@ -83,7 +83,7 @@ func (us *UserExternalLoginService) ExternalLogin(
|
|||
log.Error(err)
|
||||
}
|
||||
accessToken, _, err := us.userCommonService.CacheLoginUserInfo(
|
||||
ctx, oldUserInfo.ID, newMailStatus, oldUserInfo.Status)
|
||||
ctx, oldUserInfo.ID, newMailStatus, oldUserInfo.Status, oldExternalLoginUserInfo.ExternalID)
|
||||
return &schema.UserExternalLoginResp{AccessToken: accessToken}, err
|
||||
}
|
||||
}
|
||||
|
@ -122,7 +122,7 @@ func (us *UserExternalLoginService) ExternalLogin(
|
|||
}
|
||||
|
||||
accessToken, _, err := us.userCommonService.CacheLoginUserInfo(
|
||||
ctx, oldUserInfo.ID, newMailStatus, oldUserInfo.Status)
|
||||
ctx, oldUserInfo.ID, newMailStatus, oldUserInfo.Status, oldExternalLoginUserInfo.ExternalID)
|
||||
return &schema.UserExternalLoginResp{AccessToken: accessToken}, err
|
||||
}
|
||||
|
||||
|
@ -251,7 +251,7 @@ func (us *UserExternalLoginService) ExternalLoginBindingUserSendEmail(
|
|||
return nil, err
|
||||
}
|
||||
resp.AccessToken, _, err = us.userCommonService.CacheLoginUserInfo(
|
||||
ctx, userInfo.ID, userInfo.MailStatus, userInfo.Status)
|
||||
ctx, userInfo.ID, userInfo.MailStatus, userInfo.Status, externalLoginInfo.ExternalID)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
}
|
||||
|
|
|
@ -499,7 +499,7 @@ func (us *UserService) UserVerifyEmail(ctx context.Context, req *schema.UserVeri
|
|||
}
|
||||
|
||||
accessToken, userCacheInfo, err := us.userCommonService.CacheLoginUserInfo(
|
||||
ctx, userInfo.ID, userInfo.MailStatus, userInfo.Status)
|
||||
ctx, userInfo.ID, userInfo.MailStatus, userInfo.Status, "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
|
@ -12,6 +12,7 @@ const (
|
|||
ConfigTypeUpload ConfigType = "upload"
|
||||
ConfigTypeTimezone ConfigType = "timezone"
|
||||
ConfigTypeSwitch ConfigType = "switch"
|
||||
ConfigTypeButton ConfigType = "button"
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -43,10 +44,13 @@ type ConfigField struct {
|
|||
}
|
||||
|
||||
type ConfigFieldUIOptions struct {
|
||||
Placeholder Translator `json:"placeholder,omitempty"`
|
||||
Rows string `json:"rows,omitempty"`
|
||||
InputType InputType `json:"input_type,omitempty"`
|
||||
Label Translator `json:"label,omitempty"`
|
||||
Placeholder Translator `json:"placeholder,omitempty"`
|
||||
Rows string `json:"rows,omitempty"`
|
||||
InputType InputType `json:"input_type,omitempty"`
|
||||
Label Translator `json:"label,omitempty"`
|
||||
Action *ConfigFieldUIOptionAction `json:"action,omitempty"`
|
||||
Variant string `json:"variant,omitempty"`
|
||||
Text Translator `json:"text,omitempty"`
|
||||
}
|
||||
|
||||
type ConfigFieldOption struct {
|
||||
|
@ -54,6 +58,10 @@ type ConfigFieldOption struct {
|
|||
Value string `json:"value"`
|
||||
}
|
||||
|
||||
type ConfigFieldUIOptionAction struct {
|
||||
Url string `json:"url"`
|
||||
}
|
||||
|
||||
type Config interface {
|
||||
Base
|
||||
|
||||
|
|
|
@ -12,6 +12,8 @@ type UserCenter interface {
|
|||
SignUpCallback(ctx *GinContext) (userInfo *UserCenterBasicUserInfo, err error)
|
||||
// UserInfo returns the user information
|
||||
UserInfo(externalID string) (userInfo *UserCenterBasicUserInfo, err error)
|
||||
// UserStatus returns the latest user status
|
||||
UserStatus(externalID string) (userStatus UserStatus)
|
||||
// UserList returns the user list information
|
||||
UserList(externalIDs []string) (userInfo []*UserCenterBasicUserInfo, err error)
|
||||
// UserSettings returns the user settings
|
||||
|
@ -23,14 +25,16 @@ type UserCenter interface {
|
|||
}
|
||||
|
||||
type UserCenterDesc struct {
|
||||
Name string `json:"name"`
|
||||
Icon string `json:"icon"`
|
||||
Url string `json:"url"`
|
||||
LoginRedirectURL string `json:"login_redirect_url"`
|
||||
SignUpRedirectURL string `json:"sign_up_redirect_url"`
|
||||
RankAgentEnabled bool `json:"rank_agent_enabled"`
|
||||
RoleAgentEnabled bool `json:"role_agent_enabled"`
|
||||
MustAuthEmailEnabled bool `json:"must_auth_email_enabled"`
|
||||
Name string `json:"name"`
|
||||
DisplayName Translator `json:"display_name"`
|
||||
Icon string `json:"icon"`
|
||||
Url string `json:"url"`
|
||||
LoginRedirectURL string `json:"login_redirect_url"`
|
||||
SignUpRedirectURL string `json:"sign_up_redirect_url"`
|
||||
RankAgentEnabled bool `json:"rank_agent_enabled"`
|
||||
UserStatusAgentEnabled bool `json:"user_status_agent_enabled"`
|
||||
MustAuthEmailEnabled bool `json:"must_auth_email_enabled"`
|
||||
EnabledOriginalUserSystem bool `json:"enabled_original_user_system"`
|
||||
}
|
||||
|
||||
type UserStatus int
|
||||
|
|
|
@ -36,6 +36,7 @@ module.exports = {
|
|||
'react/no-unescaped-entities': 'off',
|
||||
'react/require-default-props': 'off',
|
||||
'arrow-body-style': 'off',
|
||||
"global-require": "off",
|
||||
'react/prop-types': 0,
|
||||
'react/no-danger': 'off',
|
||||
'jsx-a11y/no-static-element-interactions': 'off',
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
const {
|
||||
addWebpackModuleRule,
|
||||
addWebpackAlias
|
||||
addWebpackAlias,
|
||||
} = require("customize-cra");
|
||||
|
||||
const path = require("path");
|
||||
|
|
|
@ -82,7 +82,7 @@
|
|||
"react-app-rewired": "^2.2.1",
|
||||
"react-scripts": "5.0.1",
|
||||
"sass": "^1.54.4",
|
||||
"typescript": "^4.9.5",
|
||||
"typescript": "^4.8.3",
|
||||
"yaml-loader": "^0.8.0"
|
||||
},
|
||||
"packageManager": "pnpm@7.9.5",
|
||||
|
|
|
@ -66,7 +66,7 @@ specifiers:
|
|||
sass: ^1.54.4
|
||||
semver: ^7.3.8
|
||||
swr: ^1.3.0
|
||||
typescript: ^4.9.5
|
||||
typescript: ^4.8.3
|
||||
urlcat: ^3.0.0
|
||||
yaml-loader: ^0.8.0
|
||||
zustand: ^4.1.1
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover" />
|
||||
<meta name="theme-color" content="#000000" />
|
||||
<meta name="generator" content="Answer - https://github.com/answerdev/answer">
|
||||
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
|
||||
|
|
Binary file not shown.
After Width: | Height: | Size: 25 KiB |
Binary file not shown.
After Width: | Height: | Size: 12 KiB |
Binary file not shown.
After Width: | Height: | Size: 37 KiB |
Binary file not shown.
After Width: | Height: | Size: 20 KiB |
Binary file not shown.
After Width: | Height: | Size: 21 KiB |
|
@ -8,6 +8,12 @@ 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 USER_AGENT_NAMES = {
|
||||
SegmentFault: 'SegmentFault',
|
||||
WeChat: 'WeChat',
|
||||
WeCom: 'WeCom',
|
||||
DingTalk: 'DingTalk',
|
||||
};
|
||||
|
||||
export const IGNORE_PATH_LIST = [
|
||||
'/users/login',
|
||||
|
@ -74,7 +80,8 @@ export const ADMIN_NAV_MENUS = [
|
|||
name: 'themes',
|
||||
},
|
||||
{
|
||||
name: 'css-html',
|
||||
name: 'css_html',
|
||||
path: 'css-html',
|
||||
},
|
||||
],
|
||||
},
|
||||
|
@ -89,6 +96,8 @@ export const ADMIN_NAV_MENUS = [
|
|||
{ name: 'write' },
|
||||
{ name: 'seo' },
|
||||
{ name: 'login' },
|
||||
{ name: 'users', path: 'settings-users' },
|
||||
{ name: 'privileges' },
|
||||
],
|
||||
},
|
||||
{
|
||||
|
@ -96,6 +105,7 @@ export const ADMIN_NAV_MENUS = [
|
|||
children: [
|
||||
{
|
||||
name: 'installed_plugins',
|
||||
path: 'installed-plugins',
|
||||
},
|
||||
],
|
||||
},
|
||||
|
@ -599,6 +609,10 @@ export const TIMELINE_NORMAL_ACTIVITY_TYPE = [
|
|||
'upvote',
|
||||
'reopened',
|
||||
'closed',
|
||||
'pin',
|
||||
'unpin',
|
||||
'show',
|
||||
'hide',
|
||||
];
|
||||
|
||||
export const SYSTEM_AVATAR_OPTIONS = [
|
||||
|
|
|
@ -312,7 +312,6 @@ export interface HelmetUpdate extends Omit<HelmetBase, 'pageTitle'> {
|
|||
export interface AdminSettingsInterface {
|
||||
language: string;
|
||||
time_zone?: string;
|
||||
default_avatar?: string;
|
||||
}
|
||||
|
||||
export interface AdminSettingsSmtp {
|
||||
|
@ -327,6 +326,16 @@ export interface AdminSettingsSmtp {
|
|||
test_email_recipient?: string;
|
||||
}
|
||||
|
||||
export interface AdminSettingsUsers {
|
||||
allow_update_avatar: boolean;
|
||||
allow_update_bio: boolean;
|
||||
allow_update_display_name: boolean;
|
||||
allow_update_location: boolean;
|
||||
allow_update_username: boolean;
|
||||
allow_update_website: boolean;
|
||||
default_avatar: string;
|
||||
}
|
||||
|
||||
export interface SiteSettings {
|
||||
branding: AdminSettingBranding;
|
||||
general: AdminSettingsGeneral;
|
||||
|
@ -335,6 +344,7 @@ export interface SiteSettings {
|
|||
custom_css_html: AdminSettingsCustom;
|
||||
theme: AdminSettingsTheme;
|
||||
site_seo: AdminSettingsSeo;
|
||||
site_users: AdminSettingsUsers;
|
||||
version: string;
|
||||
revision: string;
|
||||
}
|
||||
|
@ -385,6 +395,7 @@ export interface AdminSettingsCustom {
|
|||
custom_head: string;
|
||||
custom_header: string;
|
||||
custom_footer: string;
|
||||
custom_sidebar: string;
|
||||
}
|
||||
|
||||
export interface AdminSettingsLogin {
|
||||
|
@ -574,3 +585,8 @@ export interface PluginConfig {
|
|||
slug_name: string;
|
||||
config_fields: PluginItem[];
|
||||
}
|
||||
|
||||
export interface QuestionOperationReq {
|
||||
id: string;
|
||||
operation: 'pin' | 'unpin' | 'hide' | 'show';
|
||||
}
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
const pattern = {
|
||||
email:
|
||||
/^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]+\.)+[a-zA-Z\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]{2,}))$/,
|
||||
uaWeChat: /micromessenger/i,
|
||||
uaWeCom: /wxwork/i,
|
||||
uaDingTalk: /dingtalk/i,
|
||||
};
|
||||
|
||||
export default pattern;
|
||||
|
|
|
@ -18,12 +18,12 @@ function MenuNode({
|
|||
}) {
|
||||
const { t } = useTranslation('translation', { keyPrefix: 'nav_menus' });
|
||||
const isLeaf = !menu.children.length;
|
||||
const href = isLeaf ? `${path}${menu.name}` : '#';
|
||||
const href = isLeaf ? `${path}${menu.path}` : '#';
|
||||
|
||||
return (
|
||||
<Nav.Item key={menu.name} className="w-100">
|
||||
<Nav.Item key={menu.path} className="w-100">
|
||||
<Nav.Link
|
||||
eventKey={menu.name}
|
||||
eventKey={menu.path}
|
||||
as={isLeaf ? 'a' : 'button'}
|
||||
onClick={(evt) => {
|
||||
callback(evt, menu, href, isLeaf);
|
||||
|
@ -31,7 +31,7 @@ function MenuNode({
|
|||
href={href}
|
||||
className={classNames(
|
||||
'text-nowrap d-flex flex-nowrap align-items-center w-100',
|
||||
{ expanding, 'link-dark': activeKey !== menu.name },
|
||||
{ expanding, 'link-dark': activeKey !== menu.path },
|
||||
)}>
|
||||
<span className="me-auto text-truncate">
|
||||
{menu.displayName ? menu.displayName : t(menu.name)}
|
||||
|
@ -44,7 +44,7 @@ function MenuNode({
|
|||
)}
|
||||
</Nav.Link>
|
||||
{menu.children.length ? (
|
||||
<Accordion.Collapse eventKey={menu.name} className="ms-3">
|
||||
<Accordion.Collapse eventKey={menu.path} className="ms-3">
|
||||
<>
|
||||
{menu.children.map((leaf) => {
|
||||
return (
|
||||
|
@ -53,7 +53,7 @@ function MenuNode({
|
|||
callback={callback}
|
||||
activeKey={activeKey}
|
||||
path={path}
|
||||
key={leaf.name}
|
||||
key={leaf.path}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
|
@ -73,17 +73,24 @@ const AccordionNav: FC<AccordionProps> = ({ menus = [], path = '/' }) => {
|
|||
const pathMatch = useMatch(`${path}*`);
|
||||
// auto set menu fields
|
||||
menus.forEach((m) => {
|
||||
if (!m.path) {
|
||||
m.path = m.name;
|
||||
}
|
||||
if (!Array.isArray(m.children)) {
|
||||
m.children = [];
|
||||
}
|
||||
m.children.forEach((sm) => {
|
||||
if (!sm.path) {
|
||||
sm.path = sm.name;
|
||||
}
|
||||
if (!Array.isArray(sm.children)) {
|
||||
sm.children = [];
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
const splat = pathMatch && pathMatch.params['*'];
|
||||
let activeKey = menus[0].name;
|
||||
let activeKey = menus[0].path;
|
||||
if (splat) {
|
||||
activeKey = splat;
|
||||
}
|
||||
|
@ -92,10 +99,10 @@ const AccordionNav: FC<AccordionProps> = ({ menus = [], path = '/' }) => {
|
|||
menus.forEach((li) => {
|
||||
if (li.children.length) {
|
||||
const matchedChild = li.children.find((el) => {
|
||||
return el.name === activeKey;
|
||||
return el.path === activeKey;
|
||||
});
|
||||
if (matchedChild) {
|
||||
openKey = li.name;
|
||||
openKey = li.path;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -111,7 +118,7 @@ const AccordionNav: FC<AccordionProps> = ({ menus = [], path = '/' }) => {
|
|||
navigate(href);
|
||||
}
|
||||
} else {
|
||||
setOpenKey(openKey === menu.name ? '' : menu.name);
|
||||
setOpenKey(openKey === menu.path ? '' : menu.path);
|
||||
}
|
||||
};
|
||||
useEffect(() => {
|
||||
|
@ -127,8 +134,8 @@ const AccordionNav: FC<AccordionProps> = ({ menus = [], path = '/' }) => {
|
|||
path={path}
|
||||
callback={menuClick}
|
||||
activeKey={activeKey}
|
||||
expanding={openKey === li.name}
|
||||
key={li.name}
|
||||
expanding={openKey === li.path}
|
||||
key={li.path}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
|
|
|
@ -9,9 +9,16 @@ interface Props {
|
|||
value: string;
|
||||
onChange: (value: string) => void;
|
||||
acceptType?: string;
|
||||
readOnly?: boolean;
|
||||
}
|
||||
|
||||
const Index: FC<Props> = ({ type = 'post', value, onChange, acceptType }) => {
|
||||
const Index: FC<Props> = ({
|
||||
type = 'post',
|
||||
value,
|
||||
onChange,
|
||||
acceptType,
|
||||
readOnly = false,
|
||||
}) => {
|
||||
const onUpload = (imgPath: string) => {
|
||||
onChange(imgPath);
|
||||
};
|
||||
|
@ -29,11 +36,15 @@ const Index: FC<Props> = ({ type = 'post', value, onChange, acceptType }) => {
|
|||
type={type}
|
||||
uploadCallback={onUpload}
|
||||
className="mb-0"
|
||||
disabled={readOnly}
|
||||
acceptType={acceptType}>
|
||||
<Icon name="cloud-upload" />
|
||||
</UploadImg>
|
||||
|
||||
<Button variant="outline-secondary" onClick={onRemove}>
|
||||
<Button
|
||||
disabled={readOnly}
|
||||
variant="outline-secondary"
|
||||
onClick={onRemove}>
|
||||
<Icon name="trash" />
|
||||
</Button>
|
||||
</ButtonGroup>
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
import { memo } from 'react';
|
||||
|
||||
import { customizeStore } from '@/stores';
|
||||
|
||||
const Index = () => {
|
||||
const { custom_sidebar } = customizeStore((state) => state);
|
||||
if (!custom_sidebar) return null;
|
||||
return <div dangerouslySetInnerHTML={{ __html: custom_sidebar }} />;
|
||||
};
|
||||
|
||||
export default memo(Index);
|
|
@ -34,7 +34,9 @@ const ActivateScriptNodes = (el, part) => {
|
|||
}
|
||||
scriptList?.forEach((so) => {
|
||||
const script = document.createElement('script');
|
||||
script.text = so.text;
|
||||
script.text = `(() => {
|
||||
${so.text}
|
||||
})();`;
|
||||
for (let i = 0; i < so.attributes.length; i += 1) {
|
||||
const attr = so.attributes[i];
|
||||
script.setAttribute(attr.name, attr.value);
|
||||
|
|
|
@ -8,6 +8,7 @@ import {
|
|||
} from 'react';
|
||||
|
||||
import { markdownToHtml } from '@/services';
|
||||
import ImgViewer from '@/components/ImgViewer';
|
||||
|
||||
import { htmlRender } from './utils';
|
||||
|
||||
|
@ -48,11 +49,13 @@ const Index = ({ value }, ref) => {
|
|||
});
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={previewRef}
|
||||
className="preview-wrap position-relative p-3 bg-light rounded text-break text-wrap mt-2 fmt"
|
||||
dangerouslySetInnerHTML={{ __html: html }}
|
||||
/>
|
||||
<ImgViewer>
|
||||
<div
|
||||
ref={previewRef}
|
||||
className="preview-wrap position-relative p-3 bg-light rounded text-break text-wrap mt-2 fmt"
|
||||
dangerouslySetInnerHTML={{ __html: html }}
|
||||
/>
|
||||
</ImgViewer>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
@ -11,8 +11,8 @@ const Index = () => {
|
|||
const siteName = siteInfoStore((state) => state.siteInfo.name);
|
||||
const cc = `${fullYear} ${siteName}`;
|
||||
return (
|
||||
<footer className="bg-light py-3">
|
||||
<Container>
|
||||
<footer className="bg-light">
|
||||
<Container className="py-3">
|
||||
<p className="text-center mb-0 fs-14 text-secondary">
|
||||
<Trans i18nKey="footer.build_on" values={{ cc }}>
|
||||
Built on
|
||||
|
|
|
@ -4,7 +4,12 @@ import { useTranslation } from 'react-i18next';
|
|||
|
||||
import { usePageTags } from '@/hooks';
|
||||
|
||||
const Index = ({ httpCode = '', errMsg = '' }) => {
|
||||
const Index = ({
|
||||
httpCode = '',
|
||||
title = '',
|
||||
errMsg = '',
|
||||
showErrorCode = true,
|
||||
}) => {
|
||||
const { t } = useTranslation('translation', { keyPrefix: 'page_error' });
|
||||
useEffect(() => {
|
||||
// auto height of container
|
||||
|
@ -31,7 +36,10 @@ const Index = ({ httpCode = '', errMsg = '' }) => {
|
|||
style={{ fontSize: '120px', lineHeight: 1.2 }}>
|
||||
(=‘x‘=)
|
||||
</div>
|
||||
<h4 className="text-center">{t('http_error', { code: httpCode })}</h4>
|
||||
{showErrorCode && (
|
||||
<h4 className="text-center">{t('http_error', { code: httpCode })}</h4>
|
||||
)}
|
||||
{title && <h4 className="text-center">{title}</h4>}
|
||||
<div className="text-center mb-3 fs-5">
|
||||
{errMsg || t(`desc_${httpCode}`)}
|
||||
</div>
|
||||
|
|
|
@ -8,15 +8,24 @@ interface IProps {
|
|||
name: string;
|
||||
className?: string;
|
||||
size?: string;
|
||||
title?: string;
|
||||
onClick?: () => void;
|
||||
}
|
||||
const Icon: FC<IProps> = ({ type = 'br', name, className, size, onClick }) => {
|
||||
const Icon: FC<IProps> = ({
|
||||
type = 'br',
|
||||
name,
|
||||
className,
|
||||
size,
|
||||
onClick,
|
||||
title = '',
|
||||
}) => {
|
||||
return (
|
||||
<i
|
||||
className={classNames(type, `bi-${name}`, className)}
|
||||
style={{ ...(size && { fontSize: size }) }}
|
||||
onClick={onClick}
|
||||
onKeyDown={onClick}
|
||||
title={title}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
.img-viewer .cursor-zoom-out {
|
||||
cursor: zoom-out !important;
|
||||
}
|
||||
|
||||
.img-viewer img:not(a img, img.broken) {
|
||||
cursor: zoom-in;
|
||||
}
|
|
@ -1,14 +1,13 @@
|
|||
import { useLayoutEffect, useState, MouseEvent, useEffect } from 'react';
|
||||
import { FC, MouseEvent, ReactNode, useEffect, useState } from 'react';
|
||||
import { Modal } from 'react-bootstrap';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
|
||||
import ReactDOM from 'react-dom/client';
|
||||
import './index.css';
|
||||
import classnames from 'classnames';
|
||||
|
||||
const div = document.createElement('div');
|
||||
const root = ReactDOM.createRoot(div);
|
||||
|
||||
const useImgViewer = () => {
|
||||
const location = useLocation();
|
||||
const Index: FC<{
|
||||
children: ReactNode;
|
||||
className?: classnames.Argument;
|
||||
}> = ({ children, className }) => {
|
||||
const [visible, setVisible] = useState(false);
|
||||
const [imgSrc, setImgSrc] = useState('');
|
||||
const onClose = () => {
|
||||
|
@ -47,8 +46,18 @@ const useImgViewer = () => {
|
|||
}
|
||||
};
|
||||
|
||||
useLayoutEffect(() => {
|
||||
root.render(
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
onClose();
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
// eslint-disable-next-line jsx-a11y/click-events-have-key-events
|
||||
<div
|
||||
className={classnames('img-viewer', className)}
|
||||
onClick={checkClickForImgView}>
|
||||
{children}
|
||||
<Modal
|
||||
show={visible}
|
||||
fullscreen
|
||||
|
@ -56,23 +65,16 @@ const useImgViewer = () => {
|
|||
scrollable
|
||||
contentClassName="bg-transparent"
|
||||
onHide={onClose}>
|
||||
<Modal.Body onClick={onClose} className="p-0 d-flex">
|
||||
<Modal.Body onClick={onClose} className="img-viewer 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,
|
||||
};
|
||||
</Modal>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default useImgViewer;
|
||||
export default Index;
|
|
@ -1,19 +1,22 @@
|
|||
import { memo, FC } from 'react';
|
||||
import { Button } from 'react-bootstrap';
|
||||
import { Button, Dropdown } from 'react-bootstrap';
|
||||
import { Link, useNavigate } from 'react-router-dom';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { Modal } from '@/components';
|
||||
import { useReportModal, useToast } from '@/hooks';
|
||||
import { QuestionOperationReq } from '@/common/interface';
|
||||
import Share from '../Share';
|
||||
import {
|
||||
deleteQuestion,
|
||||
deleteAnswer,
|
||||
editCheck,
|
||||
reopenQuestion,
|
||||
questionOpetation,
|
||||
} from '@/services';
|
||||
import { tryNormalLogged } from '@/utils/guard';
|
||||
import { floppyNavigation } from '@/utils';
|
||||
import { toastStore } from '@/stores';
|
||||
|
||||
interface IProps {
|
||||
type: 'answer' | 'question';
|
||||
|
@ -78,7 +81,7 @@ const Index: FC<IProps> = ({
|
|||
id: qid,
|
||||
}).then(() => {
|
||||
toast.onShow({
|
||||
msg: t('tip_question_deleted'),
|
||||
msg: t('post_deleted', { keyPrefix: 'messages' }),
|
||||
variant: 'success',
|
||||
});
|
||||
callback?.('delete_question');
|
||||
|
@ -134,7 +137,7 @@ const Index: FC<IProps> = ({
|
|||
question_id: qid,
|
||||
}).then(() => {
|
||||
toast.onShow({
|
||||
msg: t('success', { keyPrefix: 'question_detail.reopen' }),
|
||||
msg: t('post_reopen', { keyPrefix: 'messages' }),
|
||||
variant: 'success',
|
||||
});
|
||||
refreshQuestion();
|
||||
|
@ -143,6 +146,51 @@ const Index: FC<IProps> = ({
|
|||
});
|
||||
};
|
||||
|
||||
const handleCommon = async (params) => {
|
||||
await questionOpetation(params);
|
||||
let msg = '';
|
||||
if (params.operation === 'pin') {
|
||||
msg = t('post_pin', { keyPrefix: 'messages' });
|
||||
}
|
||||
if (params.operation === 'unpin') {
|
||||
msg = t('post_unpin', { keyPrefix: 'messages' });
|
||||
}
|
||||
if (params.operation === 'hide') {
|
||||
msg = t('post_hide_list', { keyPrefix: 'messages' });
|
||||
}
|
||||
if (params.operation === 'show') {
|
||||
msg = t('post_show_list', { keyPrefix: 'messages' });
|
||||
}
|
||||
toastStore.getState().show({
|
||||
msg,
|
||||
variant: 'success',
|
||||
});
|
||||
setTimeout(() => {
|
||||
refreshQuestion();
|
||||
}, 100);
|
||||
};
|
||||
|
||||
const handlOtherActions = (action) => {
|
||||
const params: QuestionOperationReq = {
|
||||
id: qid,
|
||||
operation: action,
|
||||
};
|
||||
|
||||
if (action === 'pin') {
|
||||
Modal.confirm({
|
||||
title: t('title', { keyPrefix: 'question_detail.pin' }),
|
||||
content: t('content', { keyPrefix: 'question_detail.pin' }),
|
||||
cancelBtnVariant: 'link',
|
||||
confirmText: t('confirm_btn', { keyPrefix: 'question_detail.pin' }),
|
||||
onConfirm: () => {
|
||||
handleCommon(params);
|
||||
},
|
||||
});
|
||||
} else {
|
||||
handleCommon(params);
|
||||
}
|
||||
};
|
||||
|
||||
const handleAction = (action) => {
|
||||
if (!tryNormalLogged(true)) {
|
||||
return;
|
||||
|
@ -162,8 +210,33 @@ const Index: FC<IProps> = ({
|
|||
if (action === 'reopen') {
|
||||
handleReopen();
|
||||
}
|
||||
|
||||
if (
|
||||
action === 'pin' ||
|
||||
action === 'unpin' ||
|
||||
action === 'hide' ||
|
||||
action === 'show'
|
||||
) {
|
||||
handlOtherActions(action);
|
||||
}
|
||||
};
|
||||
|
||||
const firstAction =
|
||||
memberActions?.filter(
|
||||
(v) =>
|
||||
v.action === 'report' || v.action === 'edit' || v.action === 'delete',
|
||||
) || [];
|
||||
const secondAction =
|
||||
memberActions?.filter(
|
||||
(v) =>
|
||||
v.action === 'close' ||
|
||||
v.action === 'reopen' ||
|
||||
v.action === 'pin' ||
|
||||
v.action === 'unpin' ||
|
||||
v.action === 'hide' ||
|
||||
v.action === 'show',
|
||||
) || [];
|
||||
|
||||
return (
|
||||
<div className="d-flex align-items-center">
|
||||
<Share
|
||||
|
@ -173,13 +246,13 @@ const Index: FC<IProps> = ({
|
|||
title={title}
|
||||
slugTitle={slugTitle}
|
||||
/>
|
||||
{memberActions?.map((item) => {
|
||||
{firstAction?.map((item) => {
|
||||
if (item.action === 'edit') {
|
||||
return (
|
||||
<Link
|
||||
key={item.action}
|
||||
to={editUrl}
|
||||
className="link-secondary p-0 fs-14 me-3"
|
||||
className="link-secondary p-0 fs-14 ms-3"
|
||||
onClick={(evt) => handleEdit(evt, editUrl)}
|
||||
style={{ lineHeight: '23px' }}>
|
||||
{item.name}
|
||||
|
@ -190,12 +263,32 @@ const Index: FC<IProps> = ({
|
|||
<Button
|
||||
key={item.action}
|
||||
variant="link"
|
||||
className="link-secondary p-0 fs-14 me-3"
|
||||
className="link-secondary p-0 fs-14 ms-3"
|
||||
onClick={() => handleAction(item.action)}>
|
||||
{item.name}
|
||||
</Button>
|
||||
);
|
||||
})}
|
||||
{secondAction.length > 0 && (
|
||||
<Dropdown className="ms-3">
|
||||
<Dropdown.Toggle
|
||||
variant="link"
|
||||
className="link-secondary p-0 fs-14 no-toggle">
|
||||
{t('action', { keyPrefix: 'question_detail' })}
|
||||
</Dropdown.Toggle>
|
||||
<Dropdown.Menu>
|
||||
{secondAction.map((item) => {
|
||||
return (
|
||||
<Dropdown.Item
|
||||
key={item.action}
|
||||
onClick={() => handleAction(item.action)}>
|
||||
{item.name}
|
||||
</Dropdown.Item>
|
||||
);
|
||||
})}
|
||||
</Dropdown.Menu>
|
||||
</Dropdown>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -14,6 +14,7 @@ import {
|
|||
QueryGroup,
|
||||
QuestionListLoader,
|
||||
Counts,
|
||||
Icon,
|
||||
} from '@/components';
|
||||
|
||||
const QuestionOrderKeys: Type.QuestionOrderBy[] = [
|
||||
|
@ -62,6 +63,13 @@ const QuestionList: FC<Props> = ({ source, data, isLoading = false }) => {
|
|||
key={li.id}
|
||||
className="bg-transparent py-3 px-0 border-start-0 border-end-0">
|
||||
<h5 className="text-wrap text-break">
|
||||
{li.pin === 2 && (
|
||||
<Icon
|
||||
name="pin-fill"
|
||||
className="me-1"
|
||||
title={t('pinned', { keyPrefix: 'btns' })}
|
||||
/>
|
||||
)}
|
||||
<NavLink
|
||||
to={pathFactory.questionLanding(li.id, li.url_title)}
|
||||
className="link-dark">
|
||||
|
|
|
@ -0,0 +1,53 @@
|
|||
import React, { FC, useState } from 'react';
|
||||
import { Button, ButtonProps } from 'react-bootstrap';
|
||||
|
||||
import { request } from '@/utils';
|
||||
import type * as Type from '@/common/interface';
|
||||
import type { UIAction } from '../index.d';
|
||||
|
||||
interface Props {
|
||||
fieldName: string;
|
||||
text: string;
|
||||
action: UIAction | undefined;
|
||||
formData: Type.FormDataType;
|
||||
readOnly: boolean;
|
||||
variant?: ButtonProps['variant'];
|
||||
size?: ButtonProps['size'];
|
||||
}
|
||||
const Index: FC<Props> = ({
|
||||
fieldName,
|
||||
action,
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
formData,
|
||||
text = '',
|
||||
readOnly = false,
|
||||
variant = 'primary',
|
||||
size,
|
||||
}) => {
|
||||
const [isLoading, setLoading] = useState(false);
|
||||
const handleAction = async () => {
|
||||
if (!action) {
|
||||
return;
|
||||
}
|
||||
setLoading(true);
|
||||
const method = action.method || 'get';
|
||||
await request[method](action.url);
|
||||
setLoading(false);
|
||||
};
|
||||
const disabled = isLoading || readOnly;
|
||||
return (
|
||||
<div className="d-flex">
|
||||
<Button
|
||||
name={fieldName}
|
||||
onClick={handleAction}
|
||||
disabled={disabled}
|
||||
size={size}
|
||||
variant={variant}>
|
||||
{text || fieldName}
|
||||
{isLoading ? '...' : ''}
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Index;
|
|
@ -0,0 +1,67 @@
|
|||
import React, { FC } from 'react';
|
||||
import { Form, Stack } from 'react-bootstrap';
|
||||
|
||||
import type * as Type from '@/common/interface';
|
||||
|
||||
interface Props {
|
||||
type: 'radio' | 'checkbox';
|
||||
fieldName: string;
|
||||
onChange?: (fd: Type.FormDataType) => void;
|
||||
enumValues: (string | boolean | number)[];
|
||||
enumNames: string[];
|
||||
formData: Type.FormDataType;
|
||||
readOnly?: boolean;
|
||||
}
|
||||
const Index: FC<Props> = ({
|
||||
type = 'radio',
|
||||
fieldName,
|
||||
onChange,
|
||||
enumValues,
|
||||
enumNames,
|
||||
formData,
|
||||
readOnly = false,
|
||||
}) => {
|
||||
const fieldObject = formData[fieldName];
|
||||
const handleCheck = (
|
||||
evt: React.ChangeEvent<HTMLInputElement>,
|
||||
index: number,
|
||||
) => {
|
||||
const { name, checked } = evt.currentTarget;
|
||||
const freshVal = checked ? enumValues?.[index] : '';
|
||||
const state = {
|
||||
...formData,
|
||||
[name]: {
|
||||
...formData[name],
|
||||
value: freshVal,
|
||||
isInvalid: false,
|
||||
},
|
||||
};
|
||||
if (typeof onChange === 'function') {
|
||||
onChange(state);
|
||||
}
|
||||
};
|
||||
return (
|
||||
<Stack direction="horizontal">
|
||||
{enumValues?.map((item, index) => {
|
||||
return (
|
||||
<Form.Check
|
||||
key={String(item)}
|
||||
inline
|
||||
type={type}
|
||||
name={fieldName}
|
||||
id={`form-${String(item)}`}
|
||||
label={enumNames?.[index]}
|
||||
checked={(fieldObject?.value || '') === item}
|
||||
feedback={fieldObject?.errorMsg}
|
||||
feedbackType="invalid"
|
||||
isInvalid={fieldObject?.isInvalid}
|
||||
disabled={readOnly}
|
||||
onChange={(evt) => handleCheck(evt, index)}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
export default Index;
|
|
@ -0,0 +1,52 @@
|
|||
import React, { FC } from 'react';
|
||||
import { Form } from 'react-bootstrap';
|
||||
|
||||
import type * as Type from '@/common/interface';
|
||||
|
||||
interface Props {
|
||||
type: string | undefined;
|
||||
placeholder: string | undefined;
|
||||
fieldName: string;
|
||||
onChange?: (fd: Type.FormDataType) => void;
|
||||
formData: Type.FormDataType;
|
||||
readOnly: boolean;
|
||||
}
|
||||
const Index: FC<Props> = ({
|
||||
type = 'text',
|
||||
placeholder = '',
|
||||
fieldName,
|
||||
onChange,
|
||||
formData,
|
||||
readOnly = false,
|
||||
}) => {
|
||||
const fieldObject = formData[fieldName];
|
||||
const handleChange = (evt: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const { name, value } = evt.currentTarget;
|
||||
const state = {
|
||||
...formData,
|
||||
[name]: {
|
||||
...formData[name],
|
||||
value,
|
||||
isInvalid: false,
|
||||
},
|
||||
};
|
||||
if (typeof onChange === 'function') {
|
||||
onChange(state);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Form.Control
|
||||
name={fieldName}
|
||||
placeholder={placeholder}
|
||||
type={type}
|
||||
value={fieldObject?.value || ''}
|
||||
onChange={handleChange}
|
||||
disabled={readOnly}
|
||||
isInvalid={fieldObject?.isInvalid}
|
||||
style={type === 'color' ? { width: '6rem' } : {}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default Index;
|
|
@ -0,0 +1,11 @@
|
|||
import { FC } from 'react';
|
||||
import { Form } from 'react-bootstrap';
|
||||
|
||||
interface Props {
|
||||
title: string;
|
||||
}
|
||||
const Index: FC<Props> = ({ title }) => {
|
||||
return <Form.Label>{title}</Form.Label>;
|
||||
};
|
||||
|
||||
export default Index;
|
|
@ -0,0 +1,58 @@
|
|||
import React, { FC } from 'react';
|
||||
import { Form } from 'react-bootstrap';
|
||||
|
||||
import type * as Type from '@/common/interface';
|
||||
|
||||
interface Props {
|
||||
desc: string | undefined;
|
||||
fieldName: string;
|
||||
onChange?: (fd: Type.FormDataType) => void;
|
||||
enumValues: (string | boolean | number)[];
|
||||
enumNames: string[];
|
||||
formData: Type.FormDataType;
|
||||
readOnly: boolean;
|
||||
}
|
||||
const Index: FC<Props> = ({
|
||||
desc,
|
||||
fieldName,
|
||||
onChange,
|
||||
enumValues,
|
||||
enumNames,
|
||||
formData,
|
||||
readOnly = false,
|
||||
}) => {
|
||||
const fieldObject = formData[fieldName];
|
||||
const handleChange = (evt: React.ChangeEvent<HTMLSelectElement>) => {
|
||||
const { name, value } = evt.currentTarget;
|
||||
const state = {
|
||||
...formData,
|
||||
[name]: {
|
||||
...formData[name],
|
||||
value,
|
||||
isInvalid: false,
|
||||
},
|
||||
};
|
||||
if (typeof onChange === 'function') {
|
||||
onChange(state);
|
||||
}
|
||||
};
|
||||
return (
|
||||
<Form.Select
|
||||
aria-label={desc}
|
||||
name={fieldName}
|
||||
value={fieldObject?.value || ''}
|
||||
onChange={handleChange}
|
||||
disabled={readOnly}
|
||||
isInvalid={fieldObject?.isInvalid}>
|
||||
{enumValues?.map((item, index) => {
|
||||
return (
|
||||
<option value={String(item)} key={String(item)}>
|
||||
{enumNames?.[index]}
|
||||
</option>
|
||||
);
|
||||
})}
|
||||
</Form.Select>
|
||||
);
|
||||
};
|
||||
|
||||
export default Index;
|
|
@ -0,0 +1,53 @@
|
|||
import React, { FC } from 'react';
|
||||
import { Form } from 'react-bootstrap';
|
||||
|
||||
import type * as Type from '@/common/interface';
|
||||
|
||||
interface Props {
|
||||
title: string;
|
||||
label: string | undefined;
|
||||
fieldName: string;
|
||||
onChange?: (fd: Type.FormDataType) => void;
|
||||
formData: Type.FormDataType;
|
||||
readOnly?: boolean;
|
||||
}
|
||||
const Index: FC<Props> = ({
|
||||
title,
|
||||
fieldName,
|
||||
onChange,
|
||||
label,
|
||||
formData,
|
||||
readOnly = false,
|
||||
}) => {
|
||||
const fieldObject = formData[fieldName];
|
||||
const handleChange = (evt: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const { name, checked } = evt.currentTarget;
|
||||
const state = {
|
||||
...formData,
|
||||
[name]: {
|
||||
...formData[name],
|
||||
value: checked,
|
||||
isInvalid: false,
|
||||
},
|
||||
};
|
||||
if (typeof onChange === 'function') {
|
||||
onChange(state);
|
||||
}
|
||||
};
|
||||
return (
|
||||
<Form.Check
|
||||
id={`switch-${title}`}
|
||||
name={fieldName}
|
||||
type="switch"
|
||||
label={label}
|
||||
checked={fieldObject?.value || ''}
|
||||
feedback={fieldObject?.errorMsg}
|
||||
feedbackType="invalid"
|
||||
isInvalid={fieldObject.isInvalid}
|
||||
disabled={readOnly}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default Index;
|
|
@ -0,0 +1,57 @@
|
|||
import React, { FC } from 'react';
|
||||
import { Form } from 'react-bootstrap';
|
||||
|
||||
import classnames from 'classnames';
|
||||
|
||||
import type * as Type from '@/common/interface';
|
||||
|
||||
interface Props {
|
||||
placeholder: string | undefined;
|
||||
rows: number | undefined;
|
||||
className: classnames.Argument;
|
||||
fieldName: string;
|
||||
onChange?: (fd: Type.FormDataType) => void;
|
||||
formData: Type.FormDataType;
|
||||
readOnly: boolean;
|
||||
}
|
||||
const Index: FC<Props> = ({
|
||||
placeholder = '',
|
||||
rows = 3,
|
||||
className,
|
||||
fieldName,
|
||||
onChange,
|
||||
formData,
|
||||
readOnly = false,
|
||||
}) => {
|
||||
const fieldObject = formData[fieldName];
|
||||
const handleChange = (evt: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const { name, value } = evt.currentTarget;
|
||||
const state = {
|
||||
...formData,
|
||||
[name]: {
|
||||
...formData[name],
|
||||
value,
|
||||
isInvalid: false,
|
||||
},
|
||||
};
|
||||
if (typeof onChange === 'function') {
|
||||
onChange(state);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Form.Control
|
||||
as="textarea"
|
||||
name={fieldName}
|
||||
placeholder={placeholder}
|
||||
value={fieldObject?.value || ''}
|
||||
onChange={handleChange}
|
||||
isInvalid={fieldObject?.isInvalid}
|
||||
rows={rows}
|
||||
disabled={readOnly}
|
||||
className={classnames(className)}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default Index;
|
|
@ -0,0 +1,44 @@
|
|||
import React, { FC } from 'react';
|
||||
|
||||
import type * as Type from '@/common/interface';
|
||||
import TimeZonePicker from '@/components/TimeZonePicker';
|
||||
|
||||
interface Props {
|
||||
fieldName: string;
|
||||
onChange?: (fd: Type.FormDataType) => void;
|
||||
formData: Type.FormDataType;
|
||||
readOnly?: boolean;
|
||||
}
|
||||
const Index: FC<Props> = ({
|
||||
fieldName,
|
||||
onChange,
|
||||
formData,
|
||||
readOnly = false,
|
||||
}) => {
|
||||
const fieldObject = formData[fieldName];
|
||||
const handleChange = (evt: React.ChangeEvent<HTMLSelectElement>) => {
|
||||
const { name, value } = evt.currentTarget;
|
||||
const state = {
|
||||
...formData,
|
||||
[name]: {
|
||||
...formData[name],
|
||||
value,
|
||||
isInvalid: false,
|
||||
},
|
||||
};
|
||||
if (typeof onChange === 'function') {
|
||||
onChange(state);
|
||||
}
|
||||
};
|
||||
return (
|
||||
<TimeZonePicker
|
||||
value={fieldObject?.value || ''}
|
||||
isInvalid={fieldObject?.isInvalid}
|
||||
name={fieldName}
|
||||
disabled={readOnly}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default Index;
|
|
@ -0,0 +1,54 @@
|
|||
import React, { FC } from 'react';
|
||||
import { Form } from 'react-bootstrap';
|
||||
|
||||
import type * as Type from '@/common/interface';
|
||||
import BrandUpload from '@/components/BrandUpload';
|
||||
|
||||
interface Props {
|
||||
type: Type.UploadType | undefined;
|
||||
acceptType: string | undefined;
|
||||
fieldName: string;
|
||||
onChange?: (fd: Type.FormDataType) => void;
|
||||
formData: Type.FormDataType;
|
||||
readOnly?: boolean;
|
||||
}
|
||||
const Index: FC<Props> = ({
|
||||
type = 'avatar',
|
||||
acceptType = '',
|
||||
fieldName,
|
||||
onChange,
|
||||
formData,
|
||||
readOnly = false,
|
||||
}) => {
|
||||
const fieldObject = formData[fieldName];
|
||||
const handleChange = (name: string, value: string) => {
|
||||
const state = {
|
||||
...formData,
|
||||
[name]: {
|
||||
...formData[name],
|
||||
value,
|
||||
},
|
||||
};
|
||||
if (typeof onChange === 'function') {
|
||||
onChange(state);
|
||||
}
|
||||
};
|
||||
return (
|
||||
<>
|
||||
<BrandUpload
|
||||
type={type}
|
||||
acceptType={acceptType}
|
||||
value={fieldObject?.value}
|
||||
readOnly={readOnly}
|
||||
onChange={(value) => handleChange(fieldName, value)}
|
||||
/>
|
||||
<Form.Control
|
||||
name={fieldName}
|
||||
className="d-none"
|
||||
isInvalid={fieldObject?.isInvalid}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default Index;
|
|
@ -0,0 +1,21 @@
|
|||
import Legend from './Legend';
|
||||
import Select from './Select';
|
||||
import Check from './Check';
|
||||
import Switch from './Switch';
|
||||
import Timezone from './Timezone';
|
||||
import Upload from './Upload';
|
||||
import Textarea from './Textarea';
|
||||
import Input from './Input';
|
||||
import Button from './Button';
|
||||
|
||||
export {
|
||||
Legend,
|
||||
Select,
|
||||
Check,
|
||||
Switch,
|
||||
Timezone,
|
||||
Upload,
|
||||
Textarea,
|
||||
Input,
|
||||
Button,
|
||||
};
|
|
@ -0,0 +1,6 @@
|
|||
export interface UIAction {
|
||||
url: string;
|
||||
method?: 'get' | 'post' | 'put' | 'delete';
|
||||
event?: 'click' | 'change';
|
||||
handler?: ({evt, formData, request}) => Promise<void>
|
||||
}
|
|
@ -1,18 +1,29 @@
|
|||
import {
|
||||
import React, {
|
||||
ForwardRefRenderFunction,
|
||||
forwardRef,
|
||||
useImperativeHandle,
|
||||
useEffect,
|
||||
} from 'react';
|
||||
import { Form, Button, Stack } from 'react-bootstrap';
|
||||
import { Form, Button, ButtonProps } from 'react-bootstrap';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import classnames from 'classnames';
|
||||
|
||||
import BrandUpload from '../BrandUpload';
|
||||
import TimeZonePicker from '../TimeZonePicker';
|
||||
import type * as Type from '@/common/interface';
|
||||
|
||||
import type { UIAction } from './index.d';
|
||||
import {
|
||||
Legend,
|
||||
Select,
|
||||
Check,
|
||||
Switch,
|
||||
Timezone,
|
||||
Upload,
|
||||
Textarea,
|
||||
Input,
|
||||
Button as CtrlButton,
|
||||
} from './components';
|
||||
|
||||
export interface JSONSchema {
|
||||
title: string;
|
||||
description?: string;
|
||||
|
@ -31,12 +42,19 @@ export interface JSONSchema {
|
|||
|
||||
export interface BaseUIOptions {
|
||||
empty?: string;
|
||||
className?: string | string[];
|
||||
// Will be appended to the className of the form component itself
|
||||
className?: classnames.Argument;
|
||||
// The className that will be attached to a form field container
|
||||
fieldClassName?: classnames.Argument;
|
||||
// Make a form component render into simplified mode
|
||||
readOnly?: boolean;
|
||||
simplify?: boolean;
|
||||
validator?: (
|
||||
value,
|
||||
formData?,
|
||||
) => Promise<string | true | void> | true | string;
|
||||
}
|
||||
|
||||
export interface InputOptions extends BaseUIOptions {
|
||||
placeholder?: string;
|
||||
inputType?:
|
||||
|
@ -78,6 +96,14 @@ export interface TextareaOptions extends BaseUIOptions {
|
|||
rows?: number;
|
||||
}
|
||||
|
||||
export interface ButtonOptions extends BaseUIOptions {
|
||||
text: string;
|
||||
icon?: string;
|
||||
action?: UIAction;
|
||||
variant?: ButtonProps['variant'];
|
||||
size?: ButtonProps['size'];
|
||||
}
|
||||
|
||||
export type UIOptions =
|
||||
| InputOptions
|
||||
| SelectOptions
|
||||
|
@ -86,7 +112,8 @@ export type UIOptions =
|
|||
| TimezoneOptions
|
||||
| CheckboxOptions
|
||||
| RadioOptions
|
||||
| TextareaOptions;
|
||||
| TextareaOptions
|
||||
| ButtonOptions;
|
||||
|
||||
export type UIWidget =
|
||||
| 'textarea'
|
||||
|
@ -96,7 +123,9 @@ export type UIWidget =
|
|||
| 'select'
|
||||
| 'upload'
|
||||
| 'timezone'
|
||||
| 'switch';
|
||||
| 'switch'
|
||||
| 'legend'
|
||||
| 'button';
|
||||
export interface UISchema {
|
||||
[key: string]: {
|
||||
'ui:widget'?: UIWidget;
|
||||
|
@ -117,6 +146,16 @@ interface IRef {
|
|||
validator: () => Promise<boolean>;
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO:
|
||||
* - Normalize and document `formData[key].hidden && 'd-none'`
|
||||
* - Normalize and document `hiddenSubmit`
|
||||
* - Improving field hints for `formData`
|
||||
* - Optimise form data updates
|
||||
* * Automatic field type conversion
|
||||
* * Dynamic field generation
|
||||
*/
|
||||
|
||||
/**
|
||||
* json schema form
|
||||
* @param schema json schema
|
||||
|
@ -139,9 +178,7 @@ const SchemaForm: ForwardRefRenderFunction<IRef, IProps> = (
|
|||
const { t } = useTranslation('translation', {
|
||||
keyPrefix: 'form',
|
||||
});
|
||||
|
||||
const { required = [], properties } = schema;
|
||||
|
||||
const { required = [], properties = {} } = schema || {};
|
||||
// check required field
|
||||
const excludes = required.filter((key) => !properties[key]);
|
||||
|
||||
|
@ -175,39 +212,6 @@ const SchemaForm: ForwardRefRenderFunction<IRef, IProps> = (
|
|||
setDefaultValueAsDomBehaviour();
|
||||
}, [formData]);
|
||||
|
||||
const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const { name, value } = e.target;
|
||||
const data = {
|
||||
...formData,
|
||||
[name]: { ...formData[name], value, isInvalid: false },
|
||||
};
|
||||
if (onChange instanceof Function) {
|
||||
onChange(data);
|
||||
}
|
||||
};
|
||||
|
||||
const handleSelectChange = (e: React.ChangeEvent<HTMLSelectElement>) => {
|
||||
const { name, value } = e.target;
|
||||
const data = {
|
||||
...formData,
|
||||
[name]: { ...formData[name], value, isInvalid: false },
|
||||
};
|
||||
if (onChange instanceof Function) {
|
||||
onChange(data);
|
||||
}
|
||||
};
|
||||
|
||||
const handleSwitchChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const { name, checked } = e.target;
|
||||
const data = {
|
||||
...formData,
|
||||
[name]: { ...formData[name], value: checked, isInvalid: false },
|
||||
};
|
||||
if (onChange instanceof Function) {
|
||||
onChange(data);
|
||||
}
|
||||
};
|
||||
|
||||
const requiredValidator = () => {
|
||||
const errors: string[] = [];
|
||||
required.forEach((key) => {
|
||||
|
@ -316,248 +320,146 @@ const SchemaForm: ForwardRefRenderFunction<IRef, IProps> = (
|
|||
}
|
||||
};
|
||||
|
||||
const handleUploadChange = (name: string, value: string) => {
|
||||
const data = { ...formData, [name]: { ...formData[name], value } };
|
||||
if (onChange instanceof Function) {
|
||||
onChange(data);
|
||||
}
|
||||
};
|
||||
|
||||
const handleInputCheck = (
|
||||
e: React.ChangeEvent<HTMLInputElement>,
|
||||
index: number,
|
||||
) => {
|
||||
const { name, checked } = e.currentTarget;
|
||||
const freshVal = checked ? schema.properties[name]?.enum?.[index] : '';
|
||||
const data = {
|
||||
...formData,
|
||||
[name]: {
|
||||
...formData[name],
|
||||
value: freshVal,
|
||||
isInvalid: false,
|
||||
},
|
||||
};
|
||||
if (onChange instanceof Function) {
|
||||
onChange(data);
|
||||
}
|
||||
};
|
||||
|
||||
useImperativeHandle(ref, () => ({
|
||||
validator,
|
||||
}));
|
||||
|
||||
if (!formData || !schema || !schema.properties) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<Form noValidate onSubmit={handleSubmit}>
|
||||
{keys.map((key) => {
|
||||
const { title, description } = properties[key];
|
||||
const {
|
||||
title,
|
||||
description,
|
||||
enum: enumValues = [],
|
||||
enumNames = [],
|
||||
} = properties[key];
|
||||
const { 'ui:widget': widget = 'input', 'ui:options': uiOpt } =
|
||||
uiSchema[key] || {};
|
||||
if (widget === 'select') {
|
||||
return (
|
||||
<Form.Group
|
||||
key={title}
|
||||
controlId={key}
|
||||
className={classnames('mb-3', formData[key].hidden && 'd-none')}>
|
||||
<Form.Label>{title}</Form.Label>
|
||||
<Form.Select
|
||||
aria-label={description}
|
||||
name={key}
|
||||
value={formData[key]?.value || ''}
|
||||
onChange={handleSelectChange}
|
||||
isInvalid={formData[key].isInvalid}>
|
||||
{properties[key].enum?.map((item, index) => {
|
||||
return (
|
||||
<option value={String(item)} key={String(item)}>
|
||||
{properties[key].enumNames?.[index]}
|
||||
</option>
|
||||
);
|
||||
})}
|
||||
</Form.Select>
|
||||
<Form.Control.Feedback type="invalid">
|
||||
{formData[key]?.errorMsg}
|
||||
</Form.Control.Feedback>
|
||||
{description && (
|
||||
<Form.Text className="text-muted">{description}</Form.Text>
|
||||
)}
|
||||
</Form.Group>
|
||||
);
|
||||
const fieldState = formData[key];
|
||||
const uiSimplify = widget === 'legend' || uiOpt?.simplify;
|
||||
let groupClassName: BaseUIOptions['fieldClassName'] = uiOpt?.simplify
|
||||
? 'mb-2'
|
||||
: 'mb-3';
|
||||
if (widget === 'legend') {
|
||||
groupClassName = 'mb-0';
|
||||
}
|
||||
|
||||
if (widget === 'checkbox' || widget === 'radio') {
|
||||
return (
|
||||
<Form.Group
|
||||
key={title}
|
||||
className={classnames('mb-3', formData[key].hidden && 'd-none')}
|
||||
controlId={key}>
|
||||
<Form.Label>{title}</Form.Label>
|
||||
<Stack direction="horizontal">
|
||||
{properties[key].enum?.map((item, index) => {
|
||||
return (
|
||||
<Form.Check
|
||||
key={String(item)}
|
||||
inline
|
||||
required
|
||||
type={widget}
|
||||
name={key}
|
||||
id={`form-${String(item)}`}
|
||||
label={properties[key].enumNames?.[index]}
|
||||
checked={(formData[key]?.value || '') === item}
|
||||
feedback={formData[key]?.errorMsg}
|
||||
feedbackType="invalid"
|
||||
isInvalid={formData[key].isInvalid}
|
||||
onChange={(e) => handleInputCheck(e, index)}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</Stack>
|
||||
<Form.Control.Feedback type="invalid">
|
||||
{formData[key]?.errorMsg}
|
||||
</Form.Control.Feedback>
|
||||
{description && (
|
||||
<Form.Text className="text-muted">{description}</Form.Text>
|
||||
)}
|
||||
</Form.Group>
|
||||
);
|
||||
if (uiOpt?.fieldClassName) {
|
||||
groupClassName = uiOpt.fieldClassName;
|
||||
}
|
||||
|
||||
if (widget === 'switch') {
|
||||
return (
|
||||
<Form.Group
|
||||
key={title}
|
||||
className={classnames('mb-3', formData[key].hidden && 'd-none')}
|
||||
controlId={key}>
|
||||
<Form.Label>{title}</Form.Label>
|
||||
<Form.Check
|
||||
required
|
||||
id={`switch-${title}`}
|
||||
name={key}
|
||||
type="switch"
|
||||
label={(uiOpt as SwitchOptions)?.label}
|
||||
checked={formData[key]?.value || ''}
|
||||
feedback={formData[key]?.errorMsg}
|
||||
feedbackType="invalid"
|
||||
isInvalid={formData[key].isInvalid}
|
||||
onChange={handleSwitchChange}
|
||||
/>
|
||||
<Form.Control.Feedback type="invalid">
|
||||
{formData[key]?.errorMsg}
|
||||
</Form.Control.Feedback>
|
||||
{description && (
|
||||
<Form.Text className="text-muted">{description}</Form.Text>
|
||||
)}
|
||||
</Form.Group>
|
||||
);
|
||||
}
|
||||
if (widget === 'timezone') {
|
||||
return (
|
||||
<Form.Group
|
||||
key={title}
|
||||
className={classnames('mb-3', formData[key].hidden && 'd-none')}
|
||||
controlId={key}>
|
||||
<Form.Label>{title}</Form.Label>
|
||||
<TimeZonePicker
|
||||
value={formData[key]?.value || ''}
|
||||
name={key}
|
||||
onChange={handleSelectChange}
|
||||
/>
|
||||
<Form.Control
|
||||
name={key}
|
||||
className="d-none"
|
||||
isInvalid={formData[key].isInvalid}
|
||||
/>
|
||||
<Form.Control.Feedback type="invalid">
|
||||
{formData[key]?.errorMsg}
|
||||
</Form.Control.Feedback>
|
||||
{description && (
|
||||
<Form.Text className="text-muted">{description}</Form.Text>
|
||||
)}
|
||||
</Form.Group>
|
||||
);
|
||||
}
|
||||
|
||||
if (widget === 'upload') {
|
||||
const options: UploadOptions = uiSchema[key]?.['ui:options'] || {};
|
||||
return (
|
||||
<Form.Group
|
||||
key={title}
|
||||
className={classnames('mb-3', formData[key].hidden && 'd-none')}
|
||||
controlId={key}>
|
||||
<Form.Label>{title}</Form.Label>
|
||||
<BrandUpload
|
||||
type={options.imageType || 'avatar'}
|
||||
acceptType={options.acceptType || ''}
|
||||
value={formData[key]?.value}
|
||||
onChange={(value) => handleUploadChange(key, value)}
|
||||
/>
|
||||
<Form.Control
|
||||
name={key}
|
||||
className="d-none"
|
||||
isInvalid={formData[key].isInvalid}
|
||||
/>
|
||||
<Form.Control.Feedback type="invalid">
|
||||
{formData[key]?.errorMsg}
|
||||
</Form.Control.Feedback>
|
||||
{description && (
|
||||
<Form.Text className="text-muted">{description}</Form.Text>
|
||||
)}
|
||||
</Form.Group>
|
||||
);
|
||||
}
|
||||
|
||||
if (widget === 'textarea') {
|
||||
const options: TextareaOptions = uiSchema[key]?.['ui:options'] || {};
|
||||
|
||||
return (
|
||||
<Form.Group
|
||||
controlId={`form-${key}`}
|
||||
key={key}
|
||||
className={classnames('mb-3', formData[key].hidden && 'd-none')}>
|
||||
<Form.Label>{title}</Form.Label>
|
||||
<Form.Control
|
||||
as="textarea"
|
||||
name={key}
|
||||
placeholder={options?.placeholder || ''}
|
||||
value={formData[key]?.value || ''}
|
||||
onChange={handleInputChange}
|
||||
isInvalid={formData[key].isInvalid}
|
||||
rows={options?.rows || 3}
|
||||
className={classnames(options.className)}
|
||||
/>
|
||||
<Form.Control.Feedback type="invalid">
|
||||
{formData[key]?.errorMsg}
|
||||
</Form.Control.Feedback>
|
||||
|
||||
{description && (
|
||||
<Form.Text className="text-muted">{description}</Form.Text>
|
||||
)}
|
||||
</Form.Group>
|
||||
);
|
||||
}
|
||||
|
||||
const options: InputOptions = uiSchema[key]?.['ui:options'] || {};
|
||||
|
||||
const readOnly = uiOpt?.readOnly || false;
|
||||
return (
|
||||
<Form.Group
|
||||
key={title}
|
||||
controlId={key}
|
||||
key={key}
|
||||
className={classnames('mb-3', formData[key].hidden && 'd-none')}>
|
||||
<Form.Label>{title}</Form.Label>
|
||||
<Form.Control
|
||||
name={key}
|
||||
placeholder={options?.placeholder || ''}
|
||||
type={options?.inputType || 'text'}
|
||||
value={formData[key]?.value || ''}
|
||||
onChange={handleInputChange}
|
||||
style={options?.inputType === 'color' ? { width: '6rem' } : {}}
|
||||
isInvalid={formData[key].isInvalid}
|
||||
/>
|
||||
className={classnames(
|
||||
groupClassName,
|
||||
formData[key].hidden ? 'd-none' : null,
|
||||
)}>
|
||||
{/* Uniform processing `label` */}
|
||||
{title && !uiSimplify ? <Form.Label>{title}</Form.Label> : null}
|
||||
{/* Handling of individual specific controls */}
|
||||
{widget === 'legend' ? <Legend title={title} /> : null}
|
||||
{widget === 'select' ? (
|
||||
<Select
|
||||
desc={description}
|
||||
fieldName={key}
|
||||
onChange={onChange}
|
||||
enumValues={enumValues}
|
||||
enumNames={enumNames}
|
||||
formData={formData}
|
||||
readOnly={readOnly}
|
||||
/>
|
||||
) : null}
|
||||
{widget === 'radio' || widget === 'checkbox' ? (
|
||||
<Check
|
||||
type={widget}
|
||||
fieldName={key}
|
||||
onChange={onChange}
|
||||
enumValues={enumValues}
|
||||
enumNames={enumNames}
|
||||
formData={formData}
|
||||
readOnly={readOnly}
|
||||
/>
|
||||
) : null}
|
||||
{widget === 'switch' ? (
|
||||
<Switch
|
||||
title={title}
|
||||
label={uiOpt && 'label' in uiOpt ? uiOpt.label : ''}
|
||||
fieldName={key}
|
||||
onChange={onChange}
|
||||
formData={formData}
|
||||
readOnly={readOnly}
|
||||
/>
|
||||
) : null}
|
||||
{widget === 'timezone' ? (
|
||||
<Timezone
|
||||
fieldName={key}
|
||||
onChange={onChange}
|
||||
formData={formData}
|
||||
readOnly={readOnly}
|
||||
/>
|
||||
) : null}
|
||||
{widget === 'upload' ? (
|
||||
<Upload
|
||||
type={
|
||||
uiOpt && 'imageType' in uiOpt ? uiOpt.imageType : undefined
|
||||
}
|
||||
acceptType={
|
||||
uiOpt && 'acceptType' in uiOpt ? uiOpt.acceptType : ''
|
||||
}
|
||||
fieldName={key}
|
||||
onChange={onChange}
|
||||
formData={formData}
|
||||
readOnly={readOnly}
|
||||
/>
|
||||
) : null}
|
||||
{widget === 'textarea' ? (
|
||||
<Textarea
|
||||
placeholder={
|
||||
uiOpt && 'placeholder' in uiOpt ? uiOpt.placeholder : ''
|
||||
}
|
||||
rows={uiOpt && 'rows' in uiOpt ? uiOpt.rows : 3}
|
||||
className={uiOpt && 'className' in uiOpt ? uiOpt.className : ''}
|
||||
fieldName={key}
|
||||
onChange={onChange}
|
||||
formData={formData}
|
||||
readOnly={readOnly}
|
||||
/>
|
||||
) : null}
|
||||
{widget === 'input' ? (
|
||||
<Input
|
||||
type={uiOpt && 'inputType' in uiOpt ? uiOpt.inputType : 'text'}
|
||||
placeholder={
|
||||
uiOpt && 'placeholder' in uiOpt ? uiOpt.placeholder : ''
|
||||
}
|
||||
fieldName={key}
|
||||
onChange={onChange}
|
||||
formData={formData}
|
||||
readOnly={readOnly}
|
||||
/>
|
||||
) : null}
|
||||
{widget === 'button' ? (
|
||||
<CtrlButton
|
||||
fieldName={key}
|
||||
text={uiOpt && 'text' in uiOpt ? uiOpt.text : ''}
|
||||
action={uiOpt && 'action' in uiOpt ? uiOpt.action : undefined}
|
||||
formData={formData}
|
||||
readOnly={readOnly}
|
||||
variant={
|
||||
uiOpt && 'variant' in uiOpt ? uiOpt.variant : undefined
|
||||
}
|
||||
size={uiOpt && 'size' in uiOpt ? uiOpt.size : undefined}
|
||||
/>
|
||||
) : null}
|
||||
{/* Unified handling of `Feedback` and `Text` */}
|
||||
<Form.Control.Feedback type="invalid">
|
||||
{formData[key]?.errorMsg}
|
||||
{fieldState?.errorMsg}
|
||||
</Form.Control.Feedback>
|
||||
|
||||
{description && (
|
||||
{description ? (
|
||||
<Form.Text className="text-muted">{description}</Form.Text>
|
||||
)}
|
||||
) : null}
|
||||
</Form.Group>
|
||||
);
|
||||
})}
|
||||
|
|
|
@ -71,7 +71,7 @@ const Index: FC<IProps> = ({ type, qid, aid, title, slugTitle = '' }) => {
|
|||
<Dropdown.Toggle
|
||||
id="dropdown-share"
|
||||
as="a"
|
||||
className="no-toggle fs-14 link-secondary pointer me-3"
|
||||
className="no-toggle fs-14 link-secondary pointer"
|
||||
onClick={() => setShow(true)}
|
||||
style={{ lineHeight: '23px' }}>
|
||||
{t('share.name')}
|
||||
|
|
|
@ -38,7 +38,7 @@ const TagSelector: FC<IProps> = ({
|
|||
const [initialValue, setInitialValue] = useState<Type.Tag[]>([...value]);
|
||||
const [currentIndex, setCurrentIndex] = useState<number>(0);
|
||||
const [repeatIndex, setRepeatIndex] = useState(-1);
|
||||
const [tag, setTag] = useState<string>('');
|
||||
const [searchValue, setSearchValue] = useState<string>('');
|
||||
const [tags, setTags] = useState<Type.Tag[] | null>(null);
|
||||
const { t } = useTranslation('translation', { keyPrefix: 'tag_selector' });
|
||||
const [visibleMenu, setVisibleMenu] = useState(false);
|
||||
|
@ -101,12 +101,12 @@ const TagSelector: FC<IProps> = ({
|
|||
const fetchTags = (str) => {
|
||||
queryTags(str).then((res) => {
|
||||
const tagArray: Type.Tag[] = filterTags(res || []);
|
||||
setTags(tagArray);
|
||||
setTags(tagArray?.length > 5 ? tagArray.slice(0, 5) : tagArray);
|
||||
});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
fetchTags(tag);
|
||||
fetchTags(searchValue);
|
||||
}, [visibleMenu]);
|
||||
|
||||
const handleClick = (val: Type.Tag) => {
|
||||
|
@ -146,7 +146,7 @@ const TagSelector: FC<IProps> = ({
|
|||
|
||||
const handleSearch = async (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const searchStr = e.currentTarget.value.replace(';', '');
|
||||
setTag(searchStr);
|
||||
setSearchValue(searchStr);
|
||||
fetchTags(searchStr);
|
||||
};
|
||||
|
||||
|
@ -172,7 +172,7 @@ const TagSelector: FC<IProps> = ({
|
|||
e.preventDefault();
|
||||
|
||||
if (tags.length === 0) {
|
||||
tagModal.onShow(tag);
|
||||
tagModal.onShow(searchValue);
|
||||
return;
|
||||
}
|
||||
if (currentIndex <= tags.length - 1) {
|
||||
|
@ -228,13 +228,14 @@ const TagSelector: FC<IProps> = ({
|
|||
<FormControl
|
||||
placeholder={t('search_tag')}
|
||||
autoFocus
|
||||
value={tag}
|
||||
value={searchValue}
|
||||
onChange={handleSearch}
|
||||
/>
|
||||
</Form>
|
||||
</Dropdown.Header>
|
||||
)}
|
||||
{showRequiredTagText &&
|
||||
{!searchValue &&
|
||||
showRequiredTagText &&
|
||||
tags &&
|
||||
tags.filter((v) => v.recommend)?.length > 0 && (
|
||||
<h6 className="dropdown-header">{t('tag_required_text')}</h6>
|
||||
|
@ -251,17 +252,17 @@ const TagSelector: FC<IProps> = ({
|
|||
</Dropdown.Item>
|
||||
);
|
||||
})}
|
||||
{tag && tags && tags.length === 0 && (
|
||||
{searchValue && tags && tags.length === 0 && (
|
||||
<Dropdown.Item disabled className="text-secondary">
|
||||
{t('no_result')}
|
||||
</Dropdown.Item>
|
||||
)}
|
||||
{!hiddenCreateBtn && tag && (
|
||||
{!hiddenCreateBtn && searchValue && (
|
||||
<Button
|
||||
variant="link"
|
||||
className="px-3 btn-no-border w-100 text-start"
|
||||
onClick={() => {
|
||||
tagModal.onShow(tag);
|
||||
tagModal.onShow(searchValue);
|
||||
}}>
|
||||
+ {t('create_btn')}
|
||||
</Button>
|
||||
|
|
|
@ -1,14 +1,17 @@
|
|||
import React, { useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import classnames from 'classnames';
|
||||
|
||||
import { uploadImage } from '@/services';
|
||||
import * as Type from '@/common/interface';
|
||||
|
||||
interface IProps {
|
||||
type: Type.UploadType;
|
||||
className?: string;
|
||||
className?: classnames.Argument;
|
||||
children?: React.ReactNode;
|
||||
acceptType?: string;
|
||||
disabled?: boolean;
|
||||
uploadCallback: (img: string) => void;
|
||||
}
|
||||
|
||||
|
@ -18,6 +21,7 @@ const Index: React.FC<IProps> = ({
|
|||
children,
|
||||
acceptType = '',
|
||||
className,
|
||||
disabled = false,
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const [status, setStatus] = useState(false);
|
||||
|
@ -47,11 +51,15 @@ const Index: React.FC<IProps> = ({
|
|||
};
|
||||
|
||||
return (
|
||||
<label className={`btn btn-outline-secondary uploadBtn ${className}`}>
|
||||
<label
|
||||
className={classnames('btn btn-outline-secondary', className, {
|
||||
disabled: !!disabled,
|
||||
})}>
|
||||
{children || (status ? t('upload_img.loading') : t('upload_img.name'))}
|
||||
<input
|
||||
type="file"
|
||||
className="d-none"
|
||||
disabled={disabled}
|
||||
accept={`image/jpeg,image/jpg,image/png,image/webp${acceptType}`}
|
||||
onChange={onChange}
|
||||
/>
|
||||
|
|
|
@ -38,6 +38,8 @@ import Counts from './Counts';
|
|||
import QuestionList from './QuestionList';
|
||||
import HotQuestions from './HotQuestions';
|
||||
import HttpErrorContent from './HttpErrorContent';
|
||||
import CustomSidebar from './CustomSidebar';
|
||||
import ImgViewer from './ImgViewer';
|
||||
|
||||
export {
|
||||
Avatar,
|
||||
|
@ -82,5 +84,7 @@ export {
|
|||
QuestionList,
|
||||
HotQuestions,
|
||||
HttpErrorContent,
|
||||
CustomSidebar,
|
||||
ImgViewer,
|
||||
};
|
||||
export type { EditorRef, JSONSchema, UISchema };
|
||||
|
|
|
@ -10,7 +10,6 @@ import useChangePasswordModal from './useChangePasswordModal';
|
|||
import usePageTags from './usePageTags';
|
||||
import useLoginRedirect from './useLoginRedirect';
|
||||
import usePromptWithUnload from './usePrompt';
|
||||
import useImgViewer from './useImgViewer';
|
||||
|
||||
export {
|
||||
useTagModal,
|
||||
|
@ -25,5 +24,4 @@ export {
|
|||
usePageTags,
|
||||
useLoginRedirect,
|
||||
usePromptWithUnload,
|
||||
useImgViewer,
|
||||
};
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue