Merge remote-tracking branch 'refs/remotes/origin/master' into develop

v1.18.x
Baris Usakli
commit 1b0c6741f8

@ -56,7 +56,7 @@ NodeBB requires the following software to be installed:
## Installation
[Please refer to platform-specific installation documentation](http://docs.nodebb.org/en/latest/installing/os.html)
[Please refer to platform-specific installation documentation](https://docs.nodebb.org/installing/os)
## Securing NodeBB

@ -26,7 +26,10 @@ if (require.main !== module) {
}
var nconf = require('nconf');
nconf.argv().env('__');
nconf.argv().env({
separator: '__',
lowerCase: true,
});
var url = require('url');
var async = require('async');

@ -142,7 +142,7 @@ function getPorts() {
process.exit();
}
var urlObject = url.parse(_url);
var port = nconf.get('port') || nconf.get('PORT') || urlObject.port || 4567;
var port = nconf.get('port') || urlObject.port || 4567;
if (!Array.isArray(port)) {
port = [port];
}

@ -55,12 +55,12 @@
"morgan": "^1.3.2",
"mousetrap": "^1.5.3",
"nconf": "~0.8.2",
"nodebb-plugin-composer-default": "4.4.14",
"nodebb-plugin-composer-default": "4.4.15",
"nodebb-plugin-dbsearch": "2.0.4",
"nodebb-plugin-emoji-extended": "1.1.1",
"nodebb-plugin-emoji-one": "1.2.1",
"nodebb-plugin-markdown": "7.1.1",
"nodebb-plugin-mentions": "2.0.3",
"nodebb-plugin-mentions": "2.1.1",
"nodebb-plugin-soundpack-default": "1.0.0",
"nodebb-plugin-spam-be-gone": "0.5.0",
"nodebb-rewards-essentials": "0.0.9",

@ -20,12 +20,12 @@
"stats.all": "全て",
"updates": "更新",
"running-version": "<strong>NodeBB v <span id = \"version\">%1 </ span> </ strong>を実行しています。",
"running-version": "<strong>NodeBB v<span id=\"version\">%1</span></strong> を実行しています。",
"keep-updated": "常に最新のセキュリティパッチとバグ修正のためにNodeBBが最新であることを確認してください。",
"up-to-date": "<p>あなたは<strong>最新の状態</ strong>です。<i class = \"fa fa-check\"> </ i> </ p>",
"up-to-date": "<p>あなたは<strong>最新の状態</strong>です。<i class=\"fa fa-check\"></i></p>",
"upgrade-available": "<p>新しいバージョン (v%1) がリリースされました。<a href=\"https://docs.nodebb.org/en/latest/upgrading/index.html\">NodeBBのアップグレード</a>を検討してください。</p>",
"prerelease-upgrade-available": "<p>これはNodeBBの旧リリースのバージョンです。新しいバージョン(v%1)がリリースされました。<a href=\"https://docs.nodebb.org/en/latest/upgrading/index.html\"> NodeBBのアップグレード</a>を検討してください。</ p>",
"prerelease-warning": "<p>これはNodeBBの<strong>プレリリース版</ strong>です。意図しないバグが発生することがあります。<i class=\"fa fa-exclamation-triangle\"></i></p>",
"prerelease-warning": "<p>これはNodeBBの<strong>プレリリース版</strong>です。意図しないバグが発生することがあります。<i class=\"fa fa-exclamation-triangle\"></i></p>",
"running-in-development": "<span>フォーラムが開発モードで動作しています。フォーラムの動作が脆弱かもしれませんので、管理者に問い合わせてください。</span>",
"notices": "通知",

@ -4,21 +4,21 @@
"not-logged-in": "로그인하지 않았습니다.",
"account-locked": "임시로 잠긴 계정입니다.",
"search-requires-login": "검색을 하기 위해서는 계정이 필요합니다. 로그인하거나 가입해 주십시오.",
"invalid-cid": "올바르지 않은 카테고리 ID입니다.",
"invalid-tid": "올바르지 않은 주제 ID입니다.",
"invalid-pid": "올바르지 않은 게시물 ID입니다.",
"invalid-cid": "올바르지 않은 게시판 ID입니다.",
"invalid-tid": "올바르지 않은 게시물 ID입니다.",
"invalid-pid": "올바르지 않은 포스트 ID입니다.",
"invalid-uid": "올바르지 않은 사용자 ID입니다.",
"invalid-username": "올바르지 않은 사용자 이름입니다.",
"invalid-username": "올바르지 않은 사용자 입니다.",
"invalid-email": "올바르지 않은 이메일입니다.",
"invalid-title": "올바르지 않은 제목입니다.",
"invalid-user-data": "올바르지 않은 사용자 정보입니다.",
"invalid-password": "올바르지 않은 비밀번호입니다.",
"invalid-login-credentials": "잘못된 로그인 정보입니다.",
"invalid-username-or-password": "사용자 이름과 패스워드를 모두 설정해주세요.",
"invalid-login-credentials": "올바르지 않은 로그인 정보입니다.",
"invalid-username-or-password": "사용자과 패스워드를 모두 설정해주세요.",
"invalid-search-term": "올바르지 않은 검색어입니다.",
"csrf-invalid": "세션이 만료되어 로그인에 실패하였습니다. 다시 시도해 주세요.",
"invalid-pagination-value": "올바르지 않은 값입니다. 최소 1%에서 최대 2%까지 설정해야 합니다.",
"username-taken": "이미 사용 중인 사용자 이름입니다.",
"invalid-pagination-value": "올바르지 않은 페이지 값입니다. 최소 1% 에서 최대 2% 사이로 설정해야 합니다.",
"username-taken": "이미 사용 중인 사용자 입니다.",
"email-taken": "이미 사용 중인 이메일입니다.",
"email-not-confirmed": "아직 이메일이 인증되지 않았습니다. 여기를 누르면 인증 메일을 발송할 수 있습니다.",
"email-not-confirmed-chat": "아직 이메일이 인증되지 않았습니다. 대화기능은 인증 후에 사용이 가능합니다.",
@ -36,49 +36,49 @@
"user-too-new": "죄송합니다, 첫 번째 게시물은 %1 초 후에 작성할 수 있습니다.",
"blacklisted-ip": "죄송하지만, 당신의 IP는 이 커뮤니티로부터 차단되었습니다. 만약 에러라는 생각이 드신다면 관리자에게 연락해주세요.",
"ban-expiry-missing": "해당 차단의 만료일을 설정 해주세요.",
"no-category": "존재하지 않는 카테고리입니다.",
"no-topic": "존재하지 않는 주제입니다.",
"no-post": "존재하지 않는 게시물입니다.",
"no-group": "존재하지 않는 그룹입니다.",
"no-user": "존재하지 않는 사용자입니다.",
"no-teaser": "존재하지 않는 미리보기입니다.",
"no-category": "존재하지 않는 게시판 입니다.",
"no-topic": "존재하지 않는 게시물 입니다.",
"no-post": "존재하지 않는 포스트 입니다.",
"no-group": "존재하지 않는 그룹 입니다.",
"no-user": "존재하지 않는 사용자 입니다.",
"no-teaser": "존재하지 않는 미리보기 입니다.",
"no-privileges": "이 작업을 할 수 있는 권한이 없습니다.",
"category-disabled": "비활성화된 카테고리입니다.",
"topic-locked": "잠긴 주제입니다.",
"post-edit-duration-expired": "게시물의 수정은 작성한 시간으로부터 %1초 후에 가능합니다.",
"post-edit-duration-expired-minutes": "게시물의 수정은 작성한 시간으로부터 %1분 후에 가능합니다.",
"post-edit-duration-expired-minutes-seconds": "게시물의 수정은 작성한 시간으로부터 %1분 %2초 후에 가능합니다.",
"post-edit-duration-expired-hours": "게시물의 수정은 작성한 시간으로부터 %1시간 후에 가능합니다.",
"post-edit-duration-expired-hours-minutes": "게시물의 수정은 작성한 시간으로부터 %1시간 %2분 후에 가능합니다.",
"post-edit-duration-expired-days": "게시물의 수정은 작성한 시간으로부터 %1일 후에 가능합니다.",
"post-edit-duration-expired-days-hours": "게시물의 수정은 작성한 시간으로부터 %1일 %2시간 후에 가능합니다.",
"post-delete-duration-expired": "게시물의 삭제는 작성한 시간으로부터 %1초 후에 가능합니다.",
"post-delete-duration-expired-minutes": "게시물의 삭제는 작성한 시간으로부터 %1분 후에 가능합니다.",
"post-delete-duration-expired-minutes-seconds": "게시물의 삭제는 작성한 시간으로부터 %1분 %2초 후에 가능합니다.",
"post-delete-duration-expired-hours": "게시물의 삭제는 작성한 시간으로부터 %1시간 후에 가능합니다.",
"post-delete-duration-expired-hours-minutes": "게시물의 삭제는 작성한 시간으로부터 %1시간 %2분 후에 가능합니다.",
"post-delete-duration-expired-days": "게시물의 삭제는 작성한 시간으로부터 %1일 후에 가능합니다.",
"post-delete-duration-expired-days-hours": "게시물의 삭제는 작성한 시간으로부터 %1일 %2시간 후에 가능합니다.",
"cant-delete-topic-has-reply": "답글이 달린 토픽은 삭제하실 수 없습니다.",
"cant-delete-topic-has-replies": "답글이 %1개 이상 달린 토픽은 삭제하실 수 없습니다.",
"content-too-short": "게시물의 내용이 너무 짧습니다. 내용은 최소 %1자 이상이어야 합니다.",
"content-too-long": "게시물의 내용이 너무 깁니다. 내용은 최대 %1자 이내로 작성할 수 있습니다.",
"category-disabled": "게시판이 비활성화 되었습니다.",
"topic-locked": "게시물이 잠겼습니다.",
"post-edit-duration-expired": "포스트의 수정은 작성한 시간으로부터 %1 초 후에 가능합니다.",
"post-edit-duration-expired-minutes": "포스트의 수정은 작성한 시간으로부터 %1분 후에 가능합니다.",
"post-edit-duration-expired-minutes-seconds": "포스트의 수정은 작성한 시간으로부터 %1분 %2초 후에 가능합니다.",
"post-edit-duration-expired-hours": "포스트의 수정은 작성한 시간으로부터 %1시간 후에 가능합니다.",
"post-edit-duration-expired-hours-minutes": "포스트의 수정은 작성한 시간으로부터 %1시간 %2분 후에 가능합니다.",
"post-edit-duration-expired-days": "포스트의 수정은 작성한 시간으로부터 %1일 후에 가능합니다.",
"post-edit-duration-expired-days-hours": "포스트의 수정은 작성한 시간으로부터 %1일 %2시간 후에 가능합니다.",
"post-delete-duration-expired": "포스트의 삭제는 작성한 시간으로부터 %1초 후에 가능합니다.",
"post-delete-duration-expired-minutes": "포스트의 삭제는 작성한 시간으로부터 %1분 후에 가능합니다.",
"post-delete-duration-expired-minutes-seconds": "포스트의 삭제는 작성한 시간으로부터 %1분 %2초 후에 가능합니다.",
"post-delete-duration-expired-hours": "포스트의 삭제는 작성한 시간으로부터 %1시간 후에 가능합니다.",
"post-delete-duration-expired-hours-minutes": "포스트의 삭제는 작성한 시간으로부터 %1시간 %2분 후에 가능합니다.",
"post-delete-duration-expired-days": "포스트의 삭제는 작성한 시간으로부터 %1일 후에 가능합니다.",
"post-delete-duration-expired-days-hours": "포스트의 삭제는 작성한 시간으로부터 %1일 %2시간 후에 가능합니다.",
"cant-delete-topic-has-reply": "답글이 달린 게시물은 삭제하실 수 없습니다.",
"cant-delete-topic-has-replies": "답글이 %1개 이상 달린 게시물은 삭제하실 수 없습니다.",
"content-too-short": "포스트의 내용이 너무 짧습니다. 내용은 최소 %1자 이상이어야 합니다.",
"content-too-long": "포스트의 내용이 너무 깁니다. 내용은 최대 %1자 이내로 작성할 수 있습니다.",
"title-too-short": "제목이 너무 짧습니다. 제목은 최소 %1자 이상이어야 합니다.",
"title-too-long": "제목이 너무 깁니다. 제목은 최대 %1자 이내로 작성할 수 있습니다.",
"category-not-selected": "선택된 게시판이 없습니다.",
"too-many-posts": "새 게시물 작성은 %1초마다 가능합니다 - 조금 천천히 작성해주세요.",
"too-many-posts-newbie": "신규 사용자는 %2 만큼의 인지도를 얻기 전까지 %1초마다 게시물을 작성할 수 있습니다. 조금 천천히 작성해주세요.",
"tag-too-short": "꼬리표가 너무 짧습니다. 꼬리표는 최소 %1자 이상이어야 합니다.",
"tag-too-long": "꼬리표가 너무 깁니다. 꼬리표는 최대 %1자 이내로 사용가능합니다.",
"not-enough-tags": "꼬리표가 없거나 부족합니다. 게시물은 %1개 이상의 꼬리표를 사용해야 합니다.",
"too-many-tags": "꼬리표가 너무 많습니다. 게시물은 %1개 이하의 꼬리표를 사용할 수 있습니다.",
"tag-too-short": "태그가 너무 짧습니다. 태그는 최소 %1자 이상이어야 합니다.",
"tag-too-long": "태그가 너무 깁니다. 태그는 최대 %1자 이내로 사용가능합니다.",
"not-enough-tags": "태그가 없거나 부족합니다. 게시물은 %1개 이상의 태그를 사용해야 합니다.",
"too-many-tags": "태그가 너무 많습니다. 게시물은 %1개 이하의 태그를 사용할 수 있습니다.",
"still-uploading": "업로드가 끝날 때까지 기다려주세요.",
"file-too-big": "업로드 가능한 파일크기는 최대 %1 KB 입니다 - 파일의 용량을 줄이거나 압축을 활용하세요.",
"guest-upload-disabled": "손님의 파일 업로드는 제한되어 있습니다.",
"already-bookmarked": "이미 즐겨찾기에 추가된 글입니다.",
"already-unbookmarked": "이미 즐겨찾기를 해제한 입니다.",
"guest-upload-disabled": "미가입 사용자의 파일 업로드는 제한되어 있습니다.",
"already-bookmarked": "이미 즐겨찾기에 추가한 포스트 입니다.",
"already-unbookmarked": "이미 즐겨찾기를 해제한 포스트 입니다.",
"cant-ban-other-admins": "다른 관리자를 차단할 수 없습니다.",
"cant-remove-last-admin": "귀하는 유일한 관리자입니다. 관리자를 그만두시기 전에 다른 사용자를 관리자로 임하세요.",
"cant-remove-last-admin": "귀하는 유일한 관리자입니다. 관리자를 그만두시기 전에 다른 사용자를 관리자로 하세요.",
"cant-delete-admin": "해당 계정을 삭제하기 전에 관리자 권한을 해제 해주십시오.",
"invalid-image-type": "올바르지 않은 이미지입니다. 사용가능한 유형: %1",
"invalid-image-extension": "올바르지 않은 이미지 확장자입니다.",
@ -86,43 +86,43 @@
"group-name-too-short": "그룹 이름이 너무 짧습니다.",
"group-name-too-long": "그룹 이름이 너무 깁니다.",
"group-already-exists": "이미 존재하는 그룹입니다.",
"group-name-change-not-allowed": "그룹 이름의 변경은 불가합니다.",
"group-name-change-not-allowed": "그룹 이름의 변경이 불가능 합니다.",
"group-already-member": "이미 이 그룹에 속해있습니다.",
"group-not-member": "이 그룹의 구성원이 아닙니다.",
"group-needs-owner": "이 그룹은 적어도 한 명의 소유자가 필요합니다.",
"group-already-invited": "이 사용자는 이미 초대됐습니다.",
"group-already-requested": "회원 요청이 이미 제출되었습니다.",
"post-already-deleted": "이미 삭제된 게시물입니다.",
"post-already-restored": "이미 복원된 게시물입니다.",
"topic-already-deleted": "이미 삭제된 주제입니다.",
"topic-already-restored": "이미 복원된 주제입니다.",
"cant-purge-main-post": "주요 게시물은 삭제할 수 없습니다. 대신 주제를 삭제하세요.",
"topic-thumbnails-are-disabled": "주제 섬네일이 이미 해제되었습니다.",
"post-already-deleted": "이미 삭제된 포스트 입니다.",
"post-already-restored": "이미 복원된 포스트 입니다.",
"topic-already-deleted": "이미 삭제된 게시물 입니다.",
"topic-already-restored": "이미 복원된 게시물 입니다.",
"cant-purge-main-post": "메인 포스트는 삭제할 수 없습니다. 대신 게시물을 삭제하세요.",
"topic-thumbnails-are-disabled": "게시물 섬네일이 비활성화 되었습니다.",
"invalid-file": "올바르지 않은 파일입니다.",
"uploads-are-disabled": "업로드는 비활성화되어 있습니다.",
"signature-too-long": "서명은 %1자 이내로 작성할 수 있습니다.",
"about-me-too-long": "자기소개는 %1자 이내로 작성할 수 있습니다.",
"uploads-are-disabled": "업로드가 비활성화 되었습니다.",
"signature-too-long": "서명은 %1 자를 넘길 수 없습니다.",
"about-me-too-long": "자기소개는 %1 자를 넘길 수 없습니다.",
"cant-chat-with-yourself": "자신과는 대화할 수 없습니다.",
"chat-restricted": "이 사용자는 대화를 제한하고 있습니다. 대화하려면 이 사용자가 귀하를 따라야합니다.",
"chat-disabled": "대화 기능을 사용하지 않습니다.",
"chat-restricted": "이 사용자는 대화를 제한하고 있습니다. 대화하려면 이 사용자가 귀하를 팔로우 해야합니다.",
"chat-disabled": "채팅 시스템이 비활성화 되었습니다.",
"too-many-messages": "짧은 시간동안 너무 많은 메시지를 전송하였습니다. 잠시 후에 다시 시도하세요.",
"invalid-chat-message": "올바르지 않은 메시지입니다.",
"chat-message-too-long": "대화 메세지는 최대 %1자로 제한됩니다.",
"cant-edit-chat-message": "편집 할 수 있는 권한이 없습니다.",
"chat-message-too-long": "채팅 메세지는 최대 %1자로 제한됩니다.",
"cant-edit-chat-message": "이 메세지를 수정 할 권한이 없습니다.",
"cant-remove-last-user": "마지막 사용자를 삭제할 수 없습니다.",
"cant-delete-chat-message": "메세지를 지울 권한이 없습니다.",
"already-voting-for-this-post": "이미 이 게시글에 투표하셨습니다.",
"reputation-system-disabled": "인지도 기능이 비활성 상태입니다.",
"cant-delete-chat-message": "이 메세지를 삭제할 권한이 없습니다.",
"already-voting-for-this-post": "이미 이 포스트에 투표하셨습니다.",
"reputation-system-disabled": "인지도 시스템이 비활성 상태입니다.",
"downvoting-disabled": "비추천 기능이 비활성 상태입니다.",
"not-enough-reputation-to-downvote": "인지도가 낮아 이 게시물에 반대할 수 없습니다.",
"not-enough-reputation-to-flag": "인지도가 낮아 이 게시물을 신고할 수 없습니다.",
"not-enough-reputation-to-downvote": "인지도가 낮아 이 포스트를 비추천할 수 없습니다.",
"not-enough-reputation-to-flag": "인지도가 낮아 이 포스트를 신고할 수 없습니다.",
"already-flagged": "이미 이 게시물을 신고했습니다.",
"reload-failed": "NodeBB 서버를 다시 읽어들이는 중 다음과 같은 문제가 발생했으나 사용자측은 지속적으로 자원을 제공받습니다. 오류 문구: \"%1\" 문제를 해결하시려면 다시 읽어들이기 전의 수정사항을 원래대로 되돌려주세요. ",
"registration-error": "등록 오류",
"parse-error": "서버 응답을 파싱하는 동안 문제가 발생했습니다.",
"parse-error": "서버로 부터의 응답을 읽는 동안 문제가 발생했습니다.",
"wrong-login-type-email": "이메일 주소를 통해 로그인하세요.",
"wrong-login-type-username": "사용자명을 통해 로그인하세요.",
"invite-maximum-met": "초대가능한 사용자를 모두 초대했습니다. (%2명 중 %1을 초대)",
"invite-maximum-met": "초대 한도 만큼의 사용자를 초대했습니다. (%2명 중 %1을 초대)",
"no-session-found": "로그인 세션을 찾을 수 없습니다.",
"not-in-room": "없는 사용자입니다.",
"no-users-in-room": "사용자가 없습니다.",

@ -7,21 +7,21 @@
"assignee": "담당자",
"update": "업데이트",
"updated": "업데이트 되었습니다.",
"target-purged": "해당 신고된 게시물은 완전 삭제 되었으며, 더이상 존재하지 않습니다.",
"target-purged": "해당 신고된 컨텐츠는 완전 삭제 되었으며, 더이상 존재하지 않습니다.",
"quick-filters": "간편 필터",
"filter-active": "적용된 하나 이상의 필터가 있습니다.",
"filter-reset": "필터 제거",
"filters": "필터 항목",
"filters": "필터 옵션",
"filter-reporterId": "신고자 ID",
"filter-targetUid": "신고된 게시물 ID",
"filter-type": "신고 유형",
"filter-type-all": "모든 컨텐츠",
"filter-type-post": "",
"filter-type-post": "포스트",
"filter-state": "처리 상태",
"filter-assignee": "담당자 ID",
"filter-cid": "게시판",
"filter-quick-mine": "나에게 배정",
"filter-quick-mine": "나에게 배정된 신고",
"filter-cid-all": "모든 게시판",
"apply-filters": "필터 적용",
@ -53,7 +53,7 @@
"modal-title": "부적절한 컨텐츠 신고",
"modal-body": "%1 %2 에 대한 신고 사유를 적어주시거나, 빠른 신고 버튼 중 하나를 사용해 주세요.",
"modal-reason-spam": "스팸",
"modal-reason-offensive": "공격적인",
"modal-reason-offensive": "부적절한 글",
"modal-reason-custom": "신고 사유",
"modal-submit": "리포트 제출",
"modal-submit-success": "이 컨텐츠는 신고되었습니다."

@ -3,7 +3,7 @@
"search": "검색",
"buttons.close": "닫기",
"403.title": "접근이 거부되었습니다.",
"403.message": "접속할 수 없는 페이지에 접근하였습니다.",
"403.message": "권한이 없는 페이지에 접속을 시도하였습니다.",
"403.login": "<a href='/login'>로그인</a>되어 있는지 확인해 주세요.",
"404.title": "페이지를 찾을 수 없습니다.",
"404.message": "존재하지 않는 페이지에 접근하였습니다. <a href='%1/'>홈 페이지</a>로 이동합니다.",
@ -15,21 +15,21 @@
"login": "로그인",
"please_log_in": "로그인해 주세요.",
"logout": "로그아웃",
"posting_restriction_info": "게시물 작성은 현재 회원에게만 제한되고 있습니다. 여기를 누르면 로그인 페이지로 이동합니다.",
"posting_restriction_info": "현재 회원들만 게시물을 작성 할 수 있습니다.여기를 누르면 로그인 페이지로 이동합니다.",
"welcome_back": "환영합니다.",
"you_have_successfully_logged_in": "성공적으로 로그인했습니다.",
"save_changes": "저장",
"save": "저장",
"close": "닫기",
"pagination": "페이지",
"pagination.out_of": "%2개 중 %1개",
"pagination.out_of": "%2 개 중 %1 개",
"pagination.enter_index": "이동할 게시물 번호를 입력하세요.",
"header.admin": "관리자",
"header.categories": "카테고리",
"header.recent": "최근 주제",
"header.unread": "읽지 않은 주제",
"header.tags": "꼬리표",
"header.popular": "인기 주제",
"header.categories": "게시판",
"header.recent": "최근 게시물",
"header.unread": "읽지 않은 게시물",
"header.tags": "태그",
"header.popular": "인기 게시물",
"header.users": "사용자",
"header.groups": "그룹",
"header.chats": "채팅",
@ -46,35 +46,35 @@
"alert.error": "오류",
"alert.banned": "차단됨",
"alert.banned.message": "이 계정은 차단되었습니다. 지금 로그아웃됩니다.",
"alert.unfollow": "더 이상 %1님을 팔로우하지 않습니다.",
"alert.follow": "%1님을 팔로우합니다.",
"alert.unfollow": "더 이상 %1 님을 팔로우 하지않습니다.",
"alert.follow": "%1 님을 팔로우 합니다.",
"online": "온라인",
"users": "사용자",
"topics": "주제",
"posts": "게시물",
"topics": "게시물",
"posts": "포스트",
"best": "베스트",
"upvoters": "추천한 유저",
"upvoted": "Upvoted",
"downvoters": "비추천한 유저",
"downvoted": "비추",
"upvoters": "추천한 사용자",
"upvoted": "추천된 게시물",
"downvoters": "비추천한 사용자",
"downvoted": "비추천된 게시물",
"views": "조회 수",
"reputation": "인기도",
"read_more": "전체 보기",
"reputation": "등급",
"read_more": " 보기",
"more": "더 보기",
"posted_ago_by_guest": "익명 사용자가 %1에 작성했습니다.",
"posted_ago_by": "%2님이 %1에 작성했습니다.",
"posted_ago": "%1에 작성되었습니다.",
"posted_in": "%1에 작성되었습니다.",
"posted_in_by": "%2님이 %1에 작성했습니다.",
"posted_in_ago": "%2 %1에 작성되었습니다. ",
"posted_in_ago_by": "%3님이 %2 %1에 작성했습니다.",
"user_posted_ago": "%1님이 %2에 작성했습니다.",
"guest_posted_ago": "익명 사용자가 %1에 작성했습니다.",
"last_edited_by": "%1님이 마지막으로 수정했습니다.",
"norecentposts": "최근 작성된 게시물이 없습니다.",
"norecenttopics": "최근 생성된 생성된 주제가 없습니다.",
"recentposts": "최근 게시물",
"recentips": "최근 로그인 IP",
"posted_ago_by_guest": "미가입 사용자가 %1 에 작성했습니다.",
"posted_ago_by": "%2 님이 %1 에 작성했습니다.",
"posted_ago": "%1 에 작성되었습니다.",
"posted_in": "%1 에 작성되었습니다.",
"posted_in_by": "%2 님이 %1 에 작성했습니다.",
"posted_in_ago": "%2 %1 에 작성되었습니다. ",
"posted_in_ago_by": "%3 님이 %2 %1 에 작성했습니다.",
"user_posted_ago": "%1 님이 %2 에 작성했습니다.",
"guest_posted_ago": "미가입 사용자가 %1 에 작성했습니다.",
"last_edited_by": "%1 님이 마지막으로 수정했습니다.",
"norecentposts": "최근 작성된 포스트가 없습니다.",
"norecenttopics": "최근 작성된 게시물이 없습니다.",
"recentposts": "최근 포스트",
"recentips": "최근 로그인 IP",
"moderator_tools": "(준)관리자 도구모음",
"away": "자리 비움",
"dnd": "방해 금지",
@ -85,7 +85,7 @@
"guest": "익명 사용자",
"guests": "익명 사용자",
"updated.title": "포럼이 업데이트 되었습니다.",
"updated.message": "이 포럼은 지금 최신 버전으로 업데이트 되었습니다. 여기를 누르면 페이지를 새로고침합니다.",
"updated.message": "이 포럼은 지금 최신 버전으로 업데이트 되었습니다. 페이지를 새로고침 하시려면 여기를 클릭해주세요.",
"privacy": "개인정보",
"follow": "팔로우",
"unfollow": "언팔로우",

@ -1,28 +1,28 @@
{
"groups": "그룹",
"view_group": "그룹 보기",
"owner": "그룹관리자",
"new_group": "그룹 생성",
"owner": "그룹 관리자",
"new_group": "새로운 그룹 만들기",
"no_groups_found": "그룹이 없습니다.",
"pending.accept": "수락",
"pending.reject": "거절",
"pending.accept_all": "전체 수락",
"pending.reject_all": "전체 거절",
"pending.none": "지금은 승인대기 회원이 없습니다.",
"pending.none": "지금은 승인 대기중인 회원이 없습니다.",
"invited.none": "지금은 초대된 회원이 없습니다.",
"invited.uninvite": "초대를 철회",
"invited.search": "그룹에 초대할 사용자 검색하세요.",
"invited.uninvite": "초대 취소",
"invited.search": "그룹에 초대할 사용자 검색",
"invited.notification_title": "<strong>%1</strong> 그룹에 초대되었습니다.",
"request.notification_title": "<strong>%1</strong> 님으로부터 그룹 구성원 요청이 들어왔습니다.",
"request.notification_text": "<strong>%1</strong> 님이 <strong>%2</strong> 의 멤버 가입을 신청했습니다.",
"request.notification_title": "<strong>%1</strong> 님으로부터 그룹 가입 요청이 들어왔습니다.",
"request.notification_text": "<strong>%1</strong> 님이 <strong>%2</strong> 가입을 신청했습니다.",
"cover-save": "저장",
"cover-saving": "저장 중",
"details.title": "그룹 상세정보",
"details.members": "구성원 목록",
"details.pending": "대기 구성원",
"details.pending": "승인 대기중인 구성원",
"details.invited": "초대된 구성원",
"details.has_no_posts": "이 그룹의 구성원이 작성한 게시물이 없습니다.",
"details.latest_posts": "최근 게시물",
"details.latest_posts": "최근 포스트",
"details.private": "비공개",
"details.disableJoinRequests": "가입 신청 비활성화하기",
"details.grant": "소유권 이전/포기하기",
@ -38,7 +38,7 @@
"details.change_colour": "컬러 변경",
"details.badge_text": "배지 문구",
"details.userTitleEnabled": "배지 보이기",
"details.private_help": "활성 구성원 가입시 그룹 관리자의 승인이 필요합니다.",
"details.private_help": "활성 시, 구성원 가입시 그룹 관리자의 승인이 필요합니다.",
"details.hidden": "숨김",
"details.hidden_help": "활성 시 그룹 목록에 노출되지 않습니다. 또한 구성원은 초대를 통해서만 가입이 가능합니다.",
"details.delete_group": "그룹 삭제",
@ -51,7 +51,7 @@
"membership.leave-group": "그룹 나가기",
"membership.reject": "거절",
"new-group.group_name": "그룹명:",
"upload-group-cover": "그룹 커버 업로드",
"upload-group-cover": "그룹 커버 사진 업로드",
"bulk-invite-instructions": "초대하고자 하는 사용자 목록을 콤마(,) 로 구분하여 기입해 주십시오.\n예: bravominski, stjohndlee, yoojkim",
"bulk-invite": "다수의 사용자 초대",
"remove_group_cover_confirm": "해당 커버 사진을 제거 하시겠습니까?"

@ -1,9 +1,9 @@
{
"username-email": "사용자 이름 / 이메일",
"username-email": "사용자 / 이메일",
"username": "사용자명",
"email": "이메일",
"remember_me": "로그인 유지",
"forgot_password": "비밀번호 초기화",
"forgot_password": "비밀번호 재설정",
"alternative_logins": "다른 방법으로 로그인",
"failed_login_attempt": "로그인 실패",
"login_successful": "성공적으로 로그인했습니다.",

@ -1,33 +1,33 @@
{
"chat.chatting_with": "<span id=\"chat-with-name\">",
"chat.chatting_with": "<span id=\"chat-with-name\"></span> 님과의 채팅",
"chat.placeholder": "메시지를 여기에 입력한 후 엔터를 눌러 전송하세요.",
"chat.send": "전송",
"chat.no_active": "활성화된 채팅이 없습니다.",
"chat.user_typing": "%1님이 입력 중입니다.",
"chat.user_has_messaged_you": "%1님이 메시지를 보냈습니다.",
"chat.see_all": "모든 대화 보기",
"chat.mark_all_read": "읽은 채팅 읽음으로 표시",
"chat.no-messages": "대화 기록을 보려면 대화 상대를 선택하세요.",
"chat.user_typing": "%1 님이 입력 중입니다.",
"chat.user_has_messaged_you": "%1 님이 메시지를 보냈습니다.",
"chat.see_all": "모든 채팅 보기",
"chat.mark_all_read": "모든 채팅 읽음으로 표시",
"chat.no-messages": "채팅 기록을 보려면 채팅 상대를 선택하세요.",
"chat.no-users-in-room": "사용자가 없습니다.",
"chat.recent-chats": "최근 대화 내용",
"chat.recent-chats": "최근 채팅",
"chat.contacts": "연락처",
"chat.message-history": "대화 기록",
"chat.pop-out": "분리된 창에서 대화",
"chat.pop-out": "분리된 창에서 채팅",
"chat.minimize": "최소화",
"chat.maximize": "최대화",
"chat.seven_days": "7일",
"chat.thirty_days": "30일",
"chat.three_months": "3개월",
"chat.delete_message_confirm": "이 대화를 삭제하시겠습니까?",
"chat.add-users-to-room": "유저 추가하기",
"chat.delete_message_confirm": "이 메세지를 삭제 하시겠습니까?",
"chat.add-users-to-room": "사용자 추가하기",
"chat.confirm-chat-with-dnd-user": "이 사용자의 상태는 \"방해금지\" 입니다. 그래도 대화를 요청 하시겠습니까?",
"composer.compose": "작성",
"composer.show_preview": "미리보기",
"composer.hide_preview": "미리보기 숨김",
"composer.user_said_in": "%1님이 %2에서 한 말:",
"composer.user_said": "%1님의 말:",
"composer.discard": "이 게시물을 지우겠습니까?",
"composer.submit_and_lock": "잠금 상태로 작성 완료",
"composer.user_said_in": "%1 님이 %2 에서 보낸 메세지:",
"composer.user_said": "%1 님의 메세지:",
"composer.discard": "이 포스트를 삭제 하시겠습니까?",
"composer.submit_and_lock": "글 작성 후 잠금",
"composer.toggle_dropdown": "내려서 확인하기",
"composer.uploading": "%1 업로드 중",
"composer.formatting.bold": "굵은글씨",

@ -3,46 +3,46 @@
"no_notifs": "새 알림이 없습니다.",
"see_all": "모든 알림 보기",
"mark_all_read": "모든 알림을 읽음 상태로 변경",
"back_to_home": "%1로 돌아가기",
"back_to_home": "%1 로 돌아가기",
"outgoing_link": "외부 링크",
"outgoing_link_message": "%1 에서 나오셨습니다.",
"outgoing_link_message": "%1 를 떠납니다.",
"continue_to": "%1 사이트로 이동",
"return_to": "%1 사이트로 돌아가기",
"new_notification": "새 알림",
"you_have_unread_notifications": "읽지 않은 알림이 있습니다.",
"all": "모든 알림",
"topics": "토픽",
"topics": "게시물",
"replies": "답글",
"chat": "대화",
"chat": "채팅",
"follows": "팔로우",
"upvote": "추천",
"new-flags": "새로 들어온 신고",
"my-flags": "내게 배정된 신고",
"bans": "접근 금지",
"bans": "차단",
"new_message_from": "<strong>%1</strong>님이 메시지를 보냈습니다.",
"upvoted_your_post_in": "<strong>%1</strong>님이 <strong>%2</strong>의 내 게시물을 추천했습니다.",
"upvoted_your_post_in_dual": "<strong>%1</strong>님과 <strong>%2</strong>님이 <strong>%3</strong>의 내 게시물을 추천했습니다.",
"upvoted_your_post_in_multiple": "<strong>%1</strong> 님과 다른 %2 명이 <strong>%3</strong>의 내 게시물을 추천했습니다.",
"moved_your_post": "<strong>%1</strong>님이 귀하의 게시물을 <strong>%2</strong>로 옮겼습니다.",
"moved_your_topic": "<strong>%1</strong> 이 <strong>%2</strong> 로 옮겨졌습니다.",
"user_flagged_post_in": "<strong>%1</strong>님이 <strong>%2</strong>의 게시물을 신고했습니다.",
"user_flagged_post_in_dual": "<strong>%1</strong> 님과 <strong>%2</strong> 님이 <strong>%3</strong> 안의 게시물에 플래그를 세웠습니다.",
"user_flagged_post_in_multiple": "<strong>%1</strong> 님과 %2 명의 다른 유저들이 <strong>%3</strong> 안의 게시물에 플래그를 세웠습니다.",
"upvoted_your_post_in": "<strong>%1</strong>님이 <strong>%2</strong>의 내 포스트를 추천했습니다.",
"upvoted_your_post_in_dual": "<strong>%1</strong>님과 <strong>%2</strong>님이 <strong>%3</strong>의 내 포스트를 추천했습니다.",
"upvoted_your_post_in_multiple": "<strong>%1</strong> 님과 다른 %2 명이 <strong>%3</strong>의 내 포스트를 추천했습니다.",
"moved_your_post": "<strong>%1</strong>님이 귀하의 포스트를 <strong>%2</strong>로 옮겼습니다.",
"moved_your_topic": "<strong>%1</strong> 이 <strong>%2</strong> 를 옮겼습니다.",
"user_flagged_post_in": "<strong>%1</strong>님이 <strong>%2</strong>에 속한 포스트를 신고했습니다.",
"user_flagged_post_in_dual": "<strong>%1</strong> 님과 <strong>%2</strong> 님이 <strong>%3</strong> 에 속한 포스트를 신고했습니다.",
"user_flagged_post_in_multiple": "<strong>%1</strong> 님과 %2 명의 다른 유저들이 <strong>%3</strong> 에 속한 포스트를 신고했습니다.",
"user_flagged_user": "<strong>%1</strong>님이 <strong>%2</strong>의 프로필을 신고했습니다.",
"user_flagged_user_dual": "<strong>%1</strong>님과 <strong>%2</strong>님이 <strong>%3</strong>의 프로필을 신고했습니다.",
"user_flagged_user_multiple": "<strong>%1</strong> 님과 다른 %2 명이 <strong>%3</strong>의 프로필을 신고했습니다.",
"user_posted_to": "<strong>%1</strong>님이 <strong>%2</strong>에 답글을 작성했습니다.",
"user_posted_to": "<strong>%1</strong>님이 <strong>%2</strong> 에 답글을 달았습니다.",
"user_posted_to_dual": "<strong>%1</strong> 님과 <strong>%2</strong> 님이 <strong>%3</strong> 에 답글을 달았습니다.",
"user_posted_to_multiple": "<strong>%1</strong> 님과 %2 명의 다른 유저들이 <strong>%3</strong> 에 답글을 달았습니다.",
"user_posted_topic": "<strong>%1</strong>님이 새 주제를 작성했습니다: <strong>%2</strong>",
"user_started_following_you": "<strong>%1</strong>님이 나를 팔로우합니다.",
"user_started_following_you_dual": "<strong>%1</strong>님과 <strong>%2</strong>님이 당신을 팔로우 시작했습니다.",
"user_started_following_you_multiple": "<strong>%1</strong>님외 %2명이 당신을 팔로우 시작했습니다.",
"user_posted_topic": "<strong>%1</strong>님이 새 게시물을 작성했습니다: <strong>%2</strong>",
"user_started_following_you": "<strong>%1</strong>님이 나를 팔로우 합니다.",
"user_started_following_you_dual": "<strong>%1</strong>님과 <strong>%2</strong>님이 나를 팔로우 합니다.",
"user_started_following_you_multiple": "<strong>%1</strong>님외 %2명이 나를 팔로우 합니다.",
"new_register": "<strong>%1</strong>님이 가입요청을 했습니다.",
"new_register_multiple": "<strong>%1<strong> 개의 회원가입 요청이 승인 대기중입니다.",
"flag_assigned_to_you": "신고 ID <strong>%1</strong> 이 배정되었습니다.",
"email-confirmed": "확인된 이메일",
"email-confirmed-message": "이메일을 인해주셔서 감사합니다. 계정이 완전히 활성화되었습니다.",
"email-confirm-error-message": "이메일 주소를 증하지 못했습니다. 코드가 올바르지 않거나 만료되었을 수 있습니다.",
"new_register_multiple": "<strong>%1</strong> 개의 회원가입 요청이 승인 대기중입니다.",
"flag_assigned_to_you": "<strong>신고 ID %1</strong> 이 나에게 배정되었습니다.",
"email-confirmed": "이메일 인증 되었습니다",
"email-confirmed-message": "이메일을 해주셔서 감사합니다. 계정이 완전히 활성화되었습니다.",
"email-confirm-error-message": "이메일 주소를 증하지 못했습니다. 코드가 올바르지 않거나 만료 되었을 수 있습니다.",
"email-confirm-sent": "확인 이메일이 발송되었습니다."
}

@ -1,51 +1,51 @@
{
"home": "홈",
"unread": "읽지 않은 주제",
"popular-day": "인기있는 주제 (오늘)",
"popular-week": "인기있는 주제 (주간)",
"popular-month": "인기있는 주제 (월간)",
"popular-alltime": "인기있는 주제",
"recent": "최근 주제",
"flagged-content": "플래그된 컨텐츠",
"unread": "읽지 않은 게시물",
"popular-day": "인기있는 게시물 (오늘)",
"popular-week": "인기있는 게시물 (주간)",
"popular-month": "인기있는 게시물 (월간)",
"popular-alltime": "인기있는 게시물",
"recent": "최근 게시물",
"flagged-content": "신고된 컨텐츠",
"ip-blacklist": "IP 블랙리스트",
"users/online": "온라인 사용자",
"users/online": "접속중인 사용자",
"users/latest": "최근 사용자",
"users/sort-posts": "가장 많은 게시물을 작성한 사용자",
"users/sort-reputation": "가장 높은 인지도를 가진 사용자",
"users/banned": "차단된 유저",
"users/most-flags": "가장 많이 플래그가 달린 사용자",
"users/sort-reputation": "가장 높은 등급을 가진 사용자",
"users/banned": "차단된 사용자",
"users/most-flags": "가장 많이 신고된 사용자",
"users/search": "사용자 검색",
"notifications": "알림",
"tags": "태그",
"tag": "\"%1\" 꼬리표가 달린 주제",
"tag": "\"%1\" 태그가 달린 게시물",
"register": "회원가입",
"registration-complete": "회원가입 완료",
"login": "로그인",
"reset": "패스워드 초기화",
"categories": "카테고리",
"reset": "패스워드 재설정",
"categories": "게시판",
"groups": "그룹",
"group": "%1 그룹",
"chats": "대화",
"chat": "%1 님과 대화",
"chats": "채팅",
"chat": "%1 님과 채팅",
"flags": "신고 목록",
"flag-details": "신고 ID %1 의 세부내용",
"account/edit": "\"%1\" 편집",
"account/edit/password": "\"%1\" 의 패스워드 변경",
"account/edit/username": "\"%1\" 의 사용자명 변경",
"account/edit/email": "\"%1\" 의 이메일 변경",
"account/edit": "\"%1\" 수정",
"account/edit/password": "\"%1\" 의 패스워드 수정",
"account/edit/username": "\"%1\" 의 사용자명 수정",
"account/edit/email": "\"%1\" 의 이메일 수정",
"account/info": "계정 정보",
"account/following": "%1 명이 팔로우",
"account/followers": "%1 명을 팔로우",
"account/posts": "%1 님이 작성한 게시물",
"account/topics": "%1 님이 생성한 주제",
"account/groups": "%1님의 그룹",
"account/bookmarks": "%1 님이 즐겨찾기 한 게시물",
"account/following": "%1 님이 팔로우 하는 사용자",
"account/followers": "%1 님을 팔로우 하는 사용자",
"account/posts": "%1 님이 작성한 포스트",
"account/topics": "%1 님이 생성한 게시물",
"account/groups": "%1 님의 그룹",
"account/bookmarks": "%1 님이 즐겨찾기 한 포스트",
"account/settings": "사용자 설정",
"account/watched": "%1님이 지켜보는 주제",
"account/upvoted": "%1 님이 upvote한 게시물",
"account/downvoted": "%1 님에 의해 Downvote된 게시물",
"account/best": "%1 님 최고의 게시물",
"confirm": "확인된 이메일",
"account/watched": "%1 님이 관심 가진 게시물",
"account/upvoted": "%1 님이 추천한 포스트",
"account/downvoted": "%1 님에 비추천한 포스트",
"account/best": "%1 님 최고의 포스트",
"confirm": "이메일 인증 되었습니다",
"maintenance.text": "%1 사이트는 현재 점검 중입니다. 나중에 다시 방문해주세요.",
"maintenance.messageIntro": "다음은 관리자가 전하는 메시지입니다.",
"throttled.text": "과도한 부하로 %1 를 로드할 수 없습니다. 잠시후에 다시 시도해주세요."

@ -1,19 +1,19 @@
{
"title": "최근 주제",
"title": "최근 게시글",
"day": "일간",
"week": "주간",
"month": "월간",
"year": "연간",
"alltime": "전체",
"no_recent_topics": "최근 생성된 주제가 없습니다.",
"no_popular_topics": "인기 주제가 없습니다.",
"there-is-a-new-topic": "새로운 주제가 있습니다.",
"there-is-a-new-topic-and-a-new-post": "새로운 주제와 게시물이 있습니다.",
"there-is-a-new-topic-and-new-posts": "새로운 주제와 %1 개의 게시물이 있습니다.",
"there-are-new-topics": "새로운 %1 개의 주제가 있습니다.",
"there-are-new-topics-and-a-new-post": "새로운 %1 개의 주제와 게시물이 있습니다.",
"there-are-new-topics-and-new-posts": "새로운 %1 개의 주제와 %2 개의 게시물이 있습니다.",
"there-is-a-new-post": "새로운 게시물이 있습니다.",
"there-are-new-posts": "새로운 %1 개의 게시물이 있습니다.",
"no_recent_topics": "최근 생성된 게시물이 없습니다.",
"no_popular_topics": "인기 게시물이 없습니다.",
"there-is-a-new-topic": "새로운 게시물이 있습니다.",
"there-is-a-new-topic-and-a-new-post": "새로운 게시물과 포스트가 있습니다.",
"there-is-a-new-topic-and-new-posts": "새로운 게시물과 %1 개의 포스트가 있습니다.",
"there-are-new-topics": "새로운 %1 개의 게시물이 있습니다.",
"there-are-new-topics-and-a-new-post": "새로운 %1 개의 게시물과 포스트가 있습니다.",
"there-are-new-topics-and-new-posts": "새로운 %1 개의 게시물과 %2 개의 포스트가 있습니다.",
"there-is-a-new-post": "새로운 포스트가 있습니다.",
"there-are-new-posts": "새로운 %1 개의 포스트가 있습니다.",
"click-here-to-reload": "이 곳을 클릭하여 새로고침 하세요."
}

@ -2,17 +2,17 @@
"register": "회원가입",
"cancel_registration": "회원가입 취소",
"help.email": "입력하신 이메일 주소는 공개되지 않으며, 설정을 통해 공개하실 수 있습니다.",
"help.username_restrictions": "%1자 이상 %2자 이하의 고유한 이름을 입력하세요. @<span id='yourUsername'>username</span> 같은 방식으로 다른 사람들을 언급할 수 있습니다.",
"help.username_restrictions": "%1자 이상 %2자 이하의 고유한 사용자명을 입력하세요. @<span id='yourUsername'>username</span> 같은 방식으로 다른 사람들을 언급할 수 있습니다.",
"help.minimum_password_length": "비밀번호는 최소 %1자로 제한됩니다.",
"email_address": "이메일",
"email_address_placeholder": "여기에 이메일 주소를 입력하세요.",
"username": "사용자 이름",
"username_placeholder": "여기에 사용자 이름을 입력하세요.",
"username": "사용자",
"username_placeholder": "여기에 사용자을 입력하세요.",
"password": "비밀번호",
"password_placeholder": "여기에 비밀번호를 입력하세요.",
"confirm_password": "비밀번호 재입력",
"confirm_password_placeholder": "여기에 비밀번호를 재입력하세요.",
"register_now_button": "회원가입",
"confirm_password": "비밀번호 확인",
"confirm_password_placeholder": "여기에 비밀번호 확인을 입력하세요.",
"register_now_button": "가입하기",
"alternative_registration": "다른 방법으로 회원가입",
"terms_of_use": "이용약관",
"agree_to_terms_of_use": "이용약관에 동의합니다.",

@ -1,17 +1,17 @@
{
"reset_password": "비밀번호 초기화",
"update_password": "비밀번호 변경",
"password_changed.title": "비밀번호가 변경되었습니다.",
"password_changed.message": "<p>성공적으로 비밀번호가 초기화되었습니다. 다시 <a href=\"/login\">로그인</a>해 주세요.",
"reset_password": "패스워드 재설정",
"update_password": "패스워드 변경",
"password_changed.title": "패스워드가 변경되었습니다.",
"password_changed.message": "<p>성공적으로 패스워드가 재설정 되었습니다. 다시 <a href=\"/login\">로그인</a>해 주세요.",
"wrong_reset_code.title": "올바르지 않은 초기화 코드입니다.",
"wrong_reset_code.message": "올바르지 않은 초기화 코드입니다. 다시 시도하거나 <a href=\"/reset\">새로운 초기화 코드를 요청</a>하세요.",
"new_password": "새 비밀번호",
"repeat_password": "비밀번호 재입력",
"enter_email": "<strong>이메일 주소</strong>를 입력하면 비밀번호를 초기화하는 방법을 메일로 알려드립니다.",
"enter_email_address": "여기에 이메일 주소를 입력하세요.",
"password_reset_sent": "이메일이 발송되었습니다.",
"wrong_reset_code.message": "올바르지 않은 재설정 코드입니다. 다시 시도하거나 <a href=\"/reset\">새로운 재설정 코드를 요청</a>하세요.",
"new_password": "새 패스워드",
"repeat_password": "패스워드 확인",
"enter_email": "<strong>이메일 주소</strong>를 입력하면 패스워드를 재설정하는 방법을 메일로 알려드립니다.",
"enter_email_address": "이메일 주소를 입력하세요.",
"password_reset_sent": "패스워드 재설정 이메일이 발송되었습니다.",
"invalid_email": "올바르지 않거나 가입되지 않은 이메일입니다.",
"password_too_short": "입력한 비밀번호가 너무 짧습니다, 다시 입력하세요.",
"passwords_do_not_match": "비밀번호와 재입력한 비밀번호가 일치하지 않습니다.",
"password_expired": "비밀번호가 만료되었습니다, 새로운 비밀번호를 입력하세요."
"password_too_short": "입력한 패스워드가 너무 짧습니다, 다시 입력하세요.",
"passwords_do_not_match": "패스워드와 패스워드 확인이 일치하지 않습니다.",
"password_expired": "패스워드가 만료되었습니다, 새로운 패스워드를 입력하세요."
}

@ -7,7 +7,7 @@
"mongo": "Mongo",
"mongo.version": "Wersja MongoDB",
"mongo.storage-engine": "Storage Engine",
"mongo.storage-engine": "Silnik Magazynowania",
"mongo.collections": "Kolekcje",
"mongo.objects": "Obiekty",
"mongo.avg-object-size": "Przybliżony rozmiar obiektu",
@ -15,7 +15,7 @@
"mongo.storage-size": "Rozmiar pamięci",
"mongo.index-size": "Rozmiar indeksu",
"mongo.file-size": "Rozmiar pliku",
"mongo.resident-memory": "Resident Memory",
"mongo.resident-memory": "Przynależna Pamięć",
"mongo.virtual-memory": "Pamięc wirtualna",
"mongo.mapped-memory": "Pamięc zmapowana",
"mongo.raw-info": "Surowe informacje MongoDB",
@ -29,8 +29,8 @@
"redis.memory-frag-ratio": "Proporcja fragmentacji pamięci",
"redis.total-connections-recieved": "Otrzymanych połączeń",
"redis.total-commands-processed": "Przetworzonych połączeń",
"redis.iops": "Instantaneous Ops. Per Second",
"redis.keyspace-hits": "Keyspace Hits",
"redis.keyspace-misses": "Keyspace Misses",
"redis.iops": "Natychmiastowe Operacje Na Sekundę",
"redis.keyspace-hits": "Trafienia Kluczy",
"redis.keyspace-misses": "Nietrafione Klucze",
"redis.raw-info": "Surowe informacje Redis"
}

@ -7,6 +7,6 @@
"custom-header.description": "Wpisz tutaj kod HTML (JavaScript, tagi <code>&lt;meta&gt;</code>) który ma być dołączony do sekcji <code>&lt;head&gt;</code> w szablonie forum.",
"custom-header.enable": "Włącz własny nagłówek",
"custom-css.livereload": "Enable Live Reload",
"custom-css.livereload.description": "Enable this to force all sessions on every device under your account to refresh whenever you click save"
"custom-css.livereload": "Włącz żywe przeładowanie",
"custom-css.livereload.description": "Włącz to, aby zmusić wszystkie sesje do odświeżenia na każdym urządzeniu na twoim koncie gdziekolwiek klikniesz zapisz."
}

@ -1,12 +1,12 @@
{
"you-are-on": "Informacja - Jesteś na <strong>%1:%2</strong>",
"nodes-responded": "%1 nodes responded within %2ms!",
"nodes-responded": "%1 nodes odpowiedziały w ciągu %2ms!",
"host": "Host",
"pid": "pid",
"nodejs": "nodejs",
"online": "online",
"git": "git",
"memory": "memory",
"memory": "pamięć",
"load": "obciążenie",
"uptime": "uptime",

@ -14,8 +14,8 @@
"dev-interested": "Zainteresowany pisanie pluginów do NodeBB??",
"docs-info": "Pełna dokumentacje dotycząca pisania pluginów znajduje się tutaj <a target=\"_blank\" href=\"https://docs.nodebb.org/en/latest/plugins/create.html\">NodeBB Docs Portal</a>.",
"order.description": "Certain plugins work ideally when they are initialised before/after other plugins.",
"order.explanation": "Plugins load in the order specified here, from top to bottom",
"order.description": "Pewne pluginy działają idealnie kiedy są zainicjalizowane przed/po innymi pluginami.",
"order.explanation": "Pluginy ładują się tutaj w określonej kolejności, od góry do dołu.",
"plugin-item.themes": "Style",
"plugin-item.deactivate": "Dezaktywować",
@ -28,7 +28,7 @@
"plugin-item.upgrade": "Zaktualizuj",
"plugin-item.more-info": "Po więcej informacji:",
"plugin-item.unknown": "Nieznane",
"plugin-item.unknown-explanation": "The state of this plugin could not be determined, possibly due to a misconfiguration error.",
"plugin-item.unknown-explanation": "Stan tego pluginu nie może być ustalony, prawdopodobnie z powodu błędu konfiguracji błędów.",
"alert.enabled": "Plugin Włączony",
"alert.disabled": "Plugin Wyłączony",
@ -40,8 +40,8 @@
"alert.upgrade-success": "Proszę przeładować NodeBB, aby poprawnie działał plugin",
"alert.install-success": "Plugin pomyślnie zainstalowany, proszę aktywować go.",
"alert.uninstall-success": "Plugin został pomyślnie zdezaktywowany oraz odinstalowany.",
"alert.suggest-error": "<p>NodeBB could not reach the package manager, proceed with installation of latest version?</p><div class=\"alert alert-danger\"><strong>Server returned (%1)</strong>: %2</div>",
"alert.package-manager-unreachable": "<p>NodeBB could not reach the package manager, an upgrade is not suggested at this time.</p>",
"alert.incompatible": "<p>Your version of NodeBB (v%1) is only cleared to upgrade to v%2 of this plugin. Please update your NodeBB if you wish to install a newer version of this plugin.</p>",
"alert.suggest-error": "<p>NodeBB nie może dostać się do menedżera pakietów, kontynuować instalacji ostatniej wersji?</p><div class=\"alert alert-danger\"><strong>Serwer zwrócił (%1)</strong>: %2</div>",
"alert.package-manager-unreachable": "<p>NodeBB nie może dostać się do menedżera pakietów, aktualizacja nie jest sugerowana w tym momencie.</p>",
"alert.incompatible": "<p>Twoja wersja NodeBB (v%1) jest usuwana w celu uaktualnienia do v%2 tego pluginu. Proszę zaktualizuj twoje NodeBB jeżeli chcesz zainstalować nowszą wersję tego pluginu.</p>",
"alert.possibly-incompatible": "<div class=\"alert alert-warning\"><p><strong>No Compatibility Information Found</strong></p><p>This plugin did not specify a specific version for installation given your NodeBB version. Full compatibility cannot be guaranteed, and may cause your NodeBB to no longer start properly.</p></div><p>In the event that NodeBB cannot boot properly:</p><pre><code>$ ./nodebb reset plugin=\"%1\"</code></pre><p>Continue installation of latest version of this plugin?</p>"
}

@ -27,7 +27,7 @@
"details.disableJoinRequests": "Wyłączono prośbę o dołączenie",
"details.grant": "Nadaj/Cofnij prawa Właściciela",
"details.kick": "Wykop",
"details.kick_confirm": "Are you sure you want to remove this member from the group?",
"details.kick_confirm": "Jesteś pewny, że chcesz wyrzucić tego użytkownika z grupy?",
"details.owner_options": "Administracja grupy",
"details.group_name": "Nazwa grupy",
"details.member_count": "Liczba Członków",

@ -20,7 +20,7 @@
"chat.three_months": "3 miesiące",
"chat.delete_message_confirm": "Jesteś pewny, że chcesz usunąć tą wiadomość?",
"chat.add-users-to-room": "Dodaj użytkownika do pokoju czatu",
"chat.confirm-chat-with-dnd-user": "This user has set their status to DnD(Do not disturb). Do you still want to chat with them?",
"chat.confirm-chat-with-dnd-user": "Ten użytkownik ustawił swój status na \"nie przeszkadzać\". Czy wciąż chcesz z nim rozmawiać?",
"composer.compose": "Twórz",
"composer.show_preview": "Pokaż Podgląd",
"composer.hide_preview": "Ukryj Podgląd",

@ -15,8 +15,8 @@ define('forum/category', [
], function (infinitescroll, share, navigator, categoryTools, sort, components, translator, topicSelect, pagination, storage) {
var Category = {};
$(window).on('action:ajaxify.end', function (ev, data) {
if (data.tpl_url !== 'category') {
$(window).on('action:ajaxify.start', function (ev, data) {
if (data.url && !data.url.startsWith('category/')) {
navigator.disable();
removeListeners();

@ -23,16 +23,14 @@ define('forum/topic', [
Topic.replaceURLTimeout = 0;
}
if (ajaxify.currentPage !== data.url) {
if (data.url && !data.url.startsWith('topic/')) {
navigator.disable();
components.get('navbar/title').find('span').text('').hide();
app.removeAlert('bookmark');
events.removeListeners();
$(window).off('keydown', onKeyDown);
}
if (data.url && !data.url.startsWith('topic/')) {
require(['search'], function (search) {
if (search.topicDOM.active) {
search.topicDOM.end();

@ -199,7 +199,7 @@ define('forum/topic/postTools', [
var selectedNode = getSelectedNode();
showStaleWarning(function () {
var username = getUserName(button);
var username = getUserSlug(button);
if (getData(button, 'data-uid') === '0' || !getData(button, 'data-userslug')) {
username = '';
}
@ -231,7 +231,7 @@ define('forum/topic/postTools', [
var selectedNode = getSelectedNode();
showStaleWarning(function () {
var username = getUserName(button);
var username = getUserSlug(button);
var toPid = getData(button, 'data-pid');
function quote(text) {
@ -284,7 +284,7 @@ define('forum/topic/postTools', [
selectedText = range.toString();
var postEl = $(content).parents('[component="post"]');
selectedPid = postEl.attr('data-pid');
username = getUserName($(content));
username = getUserSlug($(content));
range.detach();
}
return { text: selectedText, pid: selectedPid, username: username };
@ -309,22 +309,22 @@ define('forum/topic/postTools', [
return button.parents('[data-pid]').attr(data);
}
function getUserName(button) {
var username = '';
function getUserSlug(button) {
var slug = '';
var post = button.parents('[data-pid]');
if (button.attr('component') === 'topic/reply') {
return username;
return slug;
}
if (post.length) {
username = post.attr('data-username').replace(/\s/g, '-');
slug = post.attr('data-userslug');
}
if (post.length && post.attr('data-uid') !== '0') {
username = '@' + username;
slug = '@' + slug;
}
return username;
return slug;
}
function togglePostDelete(button, tid) {

@ -470,6 +470,9 @@ define('settings', function () {
}
}
// Save loaded settings into ajaxify.data for use client-side
ajaxify.data.settings = values;
$(formEl).deserialize(values);
$(formEl).find('input[type="checkbox"]').each(function () {
$(this).parents('.mdl-switch').toggleClass('is-checked', $(this).is(':checked'));
@ -510,6 +513,9 @@ define('settings', function () {
// Remove unsaved flag to re-enable ajaxify
app.flags._unsaved = false;
// Also save to local ajaxify.data
ajaxify.data.settings = values;
if (typeof callback === 'function') {
callback(err);
} else if (err) {

@ -22,6 +22,7 @@ categoryController.get = function (req, res, callback) {
var pageCount = 1;
var userPrivileges;
var settings;
var rssToken;
if ((req.params.topic_index && !utils.isNumber(req.params.topic_index)) || !utils.isNumber(cid)) {
return callback();
@ -41,10 +42,14 @@ categoryController.get = function (req, res, callback) {
userSettings: function (next) {
user.getSettings(req.uid, next);
},
rssToken: function (next) {
user.auth.getFeedToken(req.uid, next);
},
}, next);
},
function (results, next) {
userPrivileges = results.privileges;
rssToken = results.rssToken;
if (!results.categoryData.slug || (results.categoryData && parseInt(results.categoryData.disabled, 10) === 1)) {
return callback();
@ -121,15 +126,15 @@ categoryController.get = function (req, res, callback) {
categoryData.description = translator.escape(categoryData.description);
categoryData.privileges = userPrivileges;
categoryData.showSelect = categoryData.privileges.editable;
addTags(categoryData, res);
categoryData.rssFeedUrl = nconf.get('url') + '/category/' + categoryData.cid + '.rss';
if (parseInt(req.uid, 10)) {
categories.markAsRead([cid], req.uid);
categoryData.rssFeedUrl += '?uid=' + req.uid + '&token=' + rssToken;
}
addTags(categoryData, res);
categoryData['feeds:disableRSS'] = parseInt(meta.config['feeds:disableRSS'], 10) === 1;
categoryData.rssFeedUrl = nconf.get('relative_path') + '/category/' + categoryData.cid + '.rss';
categoryData.title = translator.escape(categoryData.name);
pageCount = Math.max(1, Math.ceil(categoryData.topic_count / settings.topicsPerPage));
categoryData.pagination = pagination.create(currentPage, pageCount, req.query);
@ -192,7 +197,7 @@ function addTags(categoryData, res) {
{
rel: 'alternate',
type: 'application/rss+xml',
href: nconf.get('url') + '/category/' + categoryData.cid + '.rss',
href: categoryData.rssFeedUrl,
},
{
rel: 'up',

@ -15,7 +15,7 @@ var helpers = require('./helpers');
var pagination = require('../pagination');
var utils = require('../utils');
var topicsController = {};
var topicsController = module.exports;
topicsController.get = function (req, res, callback) {
var tid = req.params.topic_id;
@ -23,6 +23,7 @@ topicsController.get = function (req, res, callback) {
var pageCount = 1;
var userPrivileges;
var settings;
var rssToken;
if ((req.params.post_index && !utils.isNumber(req.params.post_index)) || !utils.isNumber(tid)) {
return callback();
@ -40,6 +41,9 @@ topicsController.get = function (req, res, callback) {
topic: function (next) {
topics.getTopicData(tid, next);
},
rssToken: function (next) {
user.auth.getFeedToken(req.uid, next);
},
}, next);
},
function (results, next) {
@ -48,6 +52,7 @@ topicsController.get = function (req, res, callback) {
}
userPrivileges = results.privileges;
rssToken = results.rssToken;
if (!userPrivileges['topics:read'] || (parseInt(results.topic.deleted, 10) && !userPrivileges.view_deleted)) {
return helpers.notAllowed(req, res);
@ -129,167 +134,173 @@ topicsController.get = function (req, res, callback) {
plugins.fireHook('filter:controllers.topic.get', { topicData: topicData, uid: req.uid }, next);
},
function (data, next) {
var breadcrumbs = [
{
text: data.topicData.category.name,
url: nconf.get('relative_path') + '/category/' + data.topicData.category.slug,
},
{
text: data.topicData.title,
},
];
helpers.buildCategoryBreadcrumbs(data.topicData.category.parentCid, function (err, crumbs) {
if (err) {
return next(err);
}
data.topicData.breadcrumbs = crumbs.concat(breadcrumbs);
next(null, data.topicData);
});
buildBreadcrumbs(data.topicData, next);
},
function (topicData, next) {
function findPost(index) {
for (var i = 0; i < topicData.posts.length; i += 1) {
if (parseInt(topicData.posts[i].index, 10) === parseInt(index, 10)) {
return topicData.posts[i];
}
}
function (topicData) {
topicData.privileges = userPrivileges;
topicData.topicStaleDays = parseInt(meta.config.topicStaleDays, 10) || 60;
topicData['reputation:disabled'] = parseInt(meta.config['reputation:disabled'], 10) === 1;
topicData['downvote:disabled'] = parseInt(meta.config['downvote:disabled'], 10) === 1;
topicData['feeds:disableRSS'] = parseInt(meta.config['feeds:disableRSS'], 10) === 1;
topicData.bookmarkThreshold = parseInt(meta.config.bookmarkThreshold, 10) || 5;
topicData.postEditDuration = parseInt(meta.config.postEditDuration, 10) || 0;
topicData.postDeleteDuration = parseInt(meta.config.postDeleteDuration, 10) || 0;
topicData.scrollToMyPost = settings.scrollToMyPost;
topicData.rssFeedUrl = nconf.get('relative_path') + '/topic/' + topicData.tid + '.rss';
if (req.uid) {
topicData.rssFeedUrl += '?uid=' + req.uid + '&token=' + rssToken;
}
var description = '';
var postAtIndex = findPost(Math.max(0, req.params.post_index - 1));
topicData.postIndex = req.params.post_index;
topicData.pagination = pagination.create(currentPage, pageCount, req.query);
topicData.pagination.rel.forEach(function (rel) {
rel.href = nconf.get('url') + '/topic/' + topicData.slug + rel.href;
res.locals.linkTags.push(rel);
});
if (postAtIndex && postAtIndex.content) {
description = S(postAtIndex.content).decodeHTMLEntities().stripTags().s;
req.session.tids_viewed = req.session.tids_viewed || {};
if (!req.session.tids_viewed[tid] || req.session.tids_viewed[tid] < Date.now() - 3600000) {
topics.increaseViewCount(tid);
req.session.tids_viewed[tid] = Date.now();
}
if (description.length > 255) {
description = description.substr(0, 255) + '...';
}
addTags(topicData, req, res);
var ogImageUrl = '';
if (topicData.thumb) {
ogImageUrl = topicData.thumb;
} else if (postAtIndex && postAtIndex.user && postAtIndex.user.picture) {
ogImageUrl = postAtIndex.user.picture;
} else if (meta.config['og:image']) {
ogImageUrl = meta.config['og:image'];
} else if (meta.config['brand:logo']) {
ogImageUrl = meta.config['brand:logo'];
} else {
ogImageUrl = '/logo.png';
}
if (typeof ogImageUrl === 'string' && ogImageUrl.indexOf('http') === -1) {
ogImageUrl = nconf.get('url') + ogImageUrl;
if (req.uid) {
topics.markAsRead([tid], req.uid, function (err, markedRead) {
if (err) {
return callback(err);
}
if (markedRead) {
topics.pushUnreadCount(req.uid);
topics.markTopicNotificationsRead([tid], req.uid);
}
});
}
description = description.replace(/\n/g, ' ');
res.locals.metaTags = [
{
name: 'title',
content: topicData.titleRaw,
},
{
name: 'description',
content: description,
},
{
property: 'og:title',
content: topicData.titleRaw,
},
{
property: 'og:description',
content: description,
},
{
property: 'og:type',
content: 'article',
},
{
property: 'og:image',
content: ogImageUrl,
noEscape: true,
},
{
property: 'og:image:url',
content: ogImageUrl,
noEscape: true,
},
{
property: 'article:published_time',
content: utils.toISOString(topicData.timestamp),
},
{
property: 'article:modified_time',
content: utils.toISOString(topicData.lastposttime),
},
{
property: 'article:section',
content: topicData.category ? topicData.category.name : '',
},
];
res.locals.linkTags = [
{
rel: 'alternate',
type: 'application/rss+xml',
href: nconf.get('url') + '/topic/' + tid + '.rss',
},
];
res.render('topic', topicData);
},
], callback);
};
if (topicData.category) {
res.locals.linkTags.push({
rel: 'up',
href: nconf.get('url') + '/category/' + topicData.category.slug,
});
}
function buildBreadcrumbs(topicData, callback) {
var breadcrumbs = [
{
text: topicData.category.name,
url: nconf.get('relative_path') + '/category/' + topicData.category.slug,
},
{
text: topicData.title,
},
];
async.waterfall([
function (next) {
helpers.buildCategoryBreadcrumbs(topicData.category.parentCid, next);
},
function (crumbs, next) {
topicData.breadcrumbs = crumbs.concat(breadcrumbs);
next(null, topicData);
},
], function (err, data) {
if (err) {
return callback(err);
], callback);
}
function addTags(topicData, req, res) {
function findPost(index) {
for (var i = 0; i < topicData.posts.length; i += 1) {
if (parseInt(topicData.posts[i].index, 10) === parseInt(index, 10)) {
return topicData.posts[i];
}
}
}
var description = '';
var postAtIndex = findPost(Math.max(0, req.params.post_index - 1));
data.privileges = userPrivileges;
data.topicStaleDays = parseInt(meta.config.topicStaleDays, 10) || 60;
data['reputation:disabled'] = parseInt(meta.config['reputation:disabled'], 10) === 1;
data['downvote:disabled'] = parseInt(meta.config['downvote:disabled'], 10) === 1;
data['feeds:disableRSS'] = parseInt(meta.config['feeds:disableRSS'], 10) === 1;
data.bookmarkThreshold = parseInt(meta.config.bookmarkThreshold, 10) || 5;
data.postEditDuration = parseInt(meta.config.postEditDuration, 10) || 0;
data.postDeleteDuration = parseInt(meta.config.postDeleteDuration, 10) || 0;
data.scrollToMyPost = settings.scrollToMyPost;
data.rssFeedUrl = nconf.get('relative_path') + '/topic/' + data.tid + '.rss';
data.postIndex = req.params.post_index;
data.pagination = pagination.create(currentPage, pageCount, req.query);
data.pagination.rel.forEach(function (rel) {
rel.href = nconf.get('url') + '/topic/' + data.slug + rel.href;
res.locals.linkTags.push(rel);
});
if (postAtIndex && postAtIndex.content) {
description = S(postAtIndex.content).decodeHTMLEntities().stripTags().s;
}
req.session.tids_viewed = req.session.tids_viewed || {};
if (!req.session.tids_viewed[tid] || req.session.tids_viewed[tid] < Date.now() - 3600000) {
topics.increaseViewCount(tid);
req.session.tids_viewed[tid] = Date.now();
}
if (description.length > 255) {
description = description.substr(0, 255) + '...';
}
if (req.uid) {
topics.markAsRead([tid], req.uid, function (err, markedRead) {
if (err) {
return callback(err);
}
if (markedRead) {
topics.pushUnreadCount(req.uid);
topics.markTopicNotificationsRead([tid], req.uid);
}
});
}
var ogImageUrl = '';
if (topicData.thumb) {
ogImageUrl = topicData.thumb;
} else if (postAtIndex && postAtIndex.user && postAtIndex.user.picture) {
ogImageUrl = postAtIndex.user.picture;
} else if (meta.config['og:image']) {
ogImageUrl = meta.config['og:image'];
} else if (meta.config['brand:logo']) {
ogImageUrl = meta.config['brand:logo'];
} else {
ogImageUrl = '/logo.png';
}
res.render('topic', data);
});
};
if (typeof ogImageUrl === 'string' && ogImageUrl.indexOf('http') === -1) {
ogImageUrl = nconf.get('url') + ogImageUrl;
}
description = description.replace(/\n/g, ' ');
res.locals.metaTags = [
{
name: 'title',
content: topicData.titleRaw,
},
{
name: 'description',
content: description,
},
{
property: 'og:title',
content: topicData.titleRaw,
},
{
property: 'og:description',
content: description,
},
{
property: 'og:type',
content: 'article',
},
{
property: 'og:image',
content: ogImageUrl,
noEscape: true,
},
{
property: 'og:image:url',
content: ogImageUrl,
noEscape: true,
},
{
property: 'article:published_time',
content: utils.toISOString(topicData.timestamp),
},
{
property: 'article:modified_time',
content: utils.toISOString(topicData.lastposttime),
},
{
property: 'article:section',
content: topicData.category ? topicData.category.name : '',
},
];
res.locals.linkTags = [
{
rel: 'alternate',
type: 'application/rss+xml',
href: topicData.rssFeedUrl,
},
];
if (topicData.category) {
res.locals.linkTags.push({
rel: 'up',
href: nconf.get('url') + '/category/' + topicData.category.slug,
});
}
}
topicsController.teaser = function (req, res, next) {
var tid = req.params.topic_id;
@ -355,5 +366,3 @@ topicsController.pagination = function (req, res, callback) {
res.json(paginationData);
});
};
module.exports = topicsController;

@ -206,18 +206,18 @@ mongoModule.info = function (db, callback) {
stats.mem = results.serverStatus.mem;
stats.mem = results.serverStatus.mem;
stats.mem.resident = (stats.mem.resident / 1024).toFixed(2);
stats.mem.virtual = (stats.mem.virtual / 1024).toFixed(2);
stats.mem.mapped = (stats.mem.mapped / 1024).toFixed(2);
stats.mem.resident = (stats.mem.resident / 1024).toFixed(3);
stats.mem.virtual = (stats.mem.virtual / 1024).toFixed(3);
stats.mem.mapped = (stats.mem.mapped / 1024).toFixed(3);
stats.collectionData = results.listCollections;
stats.network = results.serverStatus.network;
stats.raw = JSON.stringify(stats, null, 4);
stats.avgObjSize = stats.avgObjSize.toFixed(2);
stats.dataSize = (stats.dataSize / scale).toFixed(2);
stats.storageSize = (stats.storageSize / scale).toFixed(2);
stats.fileSize = stats.fileSize ? (stats.fileSize / scale).toFixed(2) : 0;
stats.indexSize = (stats.indexSize / scale).toFixed(2);
stats.dataSize = (stats.dataSize / scale).toFixed(3);
stats.storageSize = (stats.storageSize / scale).toFixed(3);
stats.fileSize = stats.fileSize ? (stats.fileSize / scale).toFixed(3) : 0;
stats.indexSize = (stats.indexSize / scale).toFixed(3);
stats.storageEngine = results.serverStatus.storageEngine ? results.serverStatus.storageEngine.name : 'mmapv1';
stats.host = results.serverStatus.host;
stats.version = results.serverStatus.version;

@ -152,7 +152,7 @@ redisModule.info = function (cxn, callback) {
redisData[parts[0]] = parts[1];
}
});
redisData.used_memory_human = (redisData.used_memory / (1024 * 1024 * 1024)).toFixed(2);
redisData.used_memory_human = (redisData.used_memory / (1024 * 1024 * 1024)).toFixed(3);
redisData.raw = JSON.stringify(redisData, null, 4);
redisData.redis = true;

@ -36,7 +36,9 @@ Settings.set = function (hash, values, callback) {
};
Settings.setOne = function (hash, field, value, callback) {
db.setObjectField('settings:' + hash, field, value, callback);
var data = {};
data[field] = value;
Settings.set(hash, data, callback);
};
Settings.setOnEmpty = function (hash, values, callback) {
@ -54,7 +56,7 @@ Settings.setOnEmpty = function (hash, values, callback) {
});
if (Object.keys(empty).length) {
db.setObject('settings:' + hash, empty, next);
Settings.set(hash, empty, next);
} else {
next();
}

@ -26,6 +26,45 @@ module.exports = function (app, middleware) {
app.get('/tags/:tag.rss', middleware.maintenanceMode, generateForTag);
};
function validateTokenIfRequiresLogin(requiresLogin, cid, req, res, callback) {
var uid = req.query.uid;
var token = req.query.token;
if (!requiresLogin) {
return callback();
}
if (!uid || !token) {
return helpers.notAllowed(req, res);
}
async.waterfall([
function (next) {
user.getUserField(uid, 'rss_token', next);
},
function (_token, next) {
if (token === _token) {
async.waterfall([
function (next) {
privileges.categories.get(cid, uid, next);
},
function (privileges, next) {
if (!privileges.read) {
return helpers.notAllowed(req, res);
}
next();
},
], callback);
return;
}
user.auth.logAttempt(uid, req.ip, next);
},
function () {
helpers.notAllowed(req, res);
},
], callback);
}
function generateForTopic(req, res, callback) {
if (parseInt(meta.config['feeds:disableRSS'], 10) === 1) {
return controllers404.send404(req, res);
@ -33,6 +72,7 @@ function generateForTopic(req, res, callback) {
var tid = req.params.topic_id;
var userPrivileges;
var topic;
async.waterfall([
function (next) {
async.parallel({
@ -48,11 +88,12 @@ function generateForTopic(req, res, callback) {
if (!results.topic || (parseInt(results.topic.deleted, 10) && !results.privileges.view_deleted)) {
return controllers404.send404(req, res);
}
if (!results.privileges['topics:read']) {
return helpers.notAllowed(req, res);
}
userPrivileges = results.privileges;
topics.getTopicWithPosts(results.topic, 'tid:' + tid + ':posts', req.uid, 0, 25, false, next);
topic = results.topic;
validateTokenIfRequiresLogin(!results.privileges['topics:read'], results.topic.cid, req, res, next);
},
function (next) {
topics.getTopicWithPosts(topic, 'tid:' + tid + ':posts', req.uid || req.query.uid || 0, 0, 25, false, next);
},
function (topicData) {
topics.modifyPostsByPrivilege(topicData, userPrivileges);
@ -95,40 +136,12 @@ function generateForTopic(req, res, callback) {
], callback);
}
function generateForUserTopics(req, res, callback) {
if (parseInt(meta.config['feeds:disableRSS'], 10) === 1) {
return controllers404.send404(req, res);
}
var userslug = req.params.userslug;
async.waterfall([
function (next) {
user.getUidByUserslug(userslug, next);
},
function (uid, next) {
if (!uid) {
return callback();
}
user.getUserFields(uid, ['uid', 'username'], next);
},
function (userData, next) {
generateForTopics({
uid: req.uid,
title: 'Topics by ' + userData.username,
description: 'A list of topics that are posted by ' + userData.username,
feed_url: '/user/' + userslug + '/topics.rss',
site_url: '/user/' + userslug + '/topics',
}, 'uid:' + userData.uid + ':topics', req, res, next);
},
], callback);
}
function generateForCategory(req, res, next) {
if (parseInt(meta.config['feeds:disableRSS'], 10) === 1) {
return controllers404.send404(req, res);
}
var cid = req.params.category_id;
var category;
async.waterfall([
function (next) {
@ -143,22 +156,23 @@ function generateForCategory(req, res, next) {
reverse: true,
start: 0,
stop: 25,
uid: req.uid,
uid: req.uid || req.query.uid || 0,
}, next);
},
}, next);
},
function (results, next) {
if (!results.privileges.read) {
return helpers.notAllowed(req, res);
}
category = results.category;
validateTokenIfRequiresLogin(!results.privileges.read, cid, req, res, next);
},
function (next) {
generateTopicsFeed({
uid: req.uid,
title: results.category.name,
description: results.category.description,
uid: req.uid || req.query.uid || 0,
title: category.name,
description: category.description,
feed_url: '/category/' + cid + '.rss',
site_url: '/category/' + results.category.cid,
}, results.category.topics, next);
site_url: '/category/' + category.cid,
}, category.topics, next);
},
function (feed) {
sendFeed(feed, res);
@ -292,12 +306,13 @@ function generateForRecentPosts(req, res, next) {
], next);
}
function generateForCategoryRecentPosts(req, res, next) {
function generateForCategoryRecentPosts(req, res, callback) {
if (parseInt(meta.config['feeds:disableRSS'], 10) === 1) {
return controllers404.send404(req, res);
}
var cid = req.params.category_id;
var category;
var posts;
async.waterfall([
function (next) {
async.parallel({
@ -308,29 +323,29 @@ function generateForCategoryRecentPosts(req, res, next) {
categories.getCategoryData(cid, next);
},
posts: function (next) {
categories.getRecentReplies(cid, req.uid, 20, next);
categories.getRecentReplies(cid, req.uid || req.query.uid || 0, 20, next);
},
}, next);
},
function (results, next) {
if (!results.category) {
return next();
}
if (!results.privileges.read) {
return helpers.notAllowed(req, res);
return controllers404.send404(req, res);
}
category = results.category;
posts = results.posts;
validateTokenIfRequiresLogin(!results.privileges.read, cid, req, res, next);
},
function () {
var feed = generateForPostsFeed({
title: results.category.name + ' Recent Posts',
description: 'A list of recent posts from ' + results.category.name,
title: category.name + ' Recent Posts',
description: 'A list of recent posts from ' + category.name,
feed_url: '/category/' + cid + '/recentposts.rss',
site_url: '/category/' + cid + '/recentposts',
}, results.posts);
}, posts);
sendFeed(feed, res);
},
], next);
], callback);
}
function generateForPostsFeed(feedOptions, posts) {
@ -357,6 +372,35 @@ function generateForPostsFeed(feedOptions, posts) {
return feed;
}
function generateForUserTopics(req, res, callback) {
if (parseInt(meta.config['feeds:disableRSS'], 10) === 1) {
return controllers404.send404(req, res);
}
var userslug = req.params.userslug;
async.waterfall([
function (next) {
user.getUidByUserslug(userslug, next);
},
function (uid, next) {
if (!uid) {
return callback();
}
user.getUserFields(uid, ['uid', 'username'], next);
},
function (userData, next) {
generateForTopics({
uid: req.uid,
title: 'Topics by ' + userData.username,
description: 'A list of topics that are posted by ' + userData.username,
feed_url: '/user/' + userslug + '/topics.rss',
site_url: '/user/' + userslug + '/topics',
}, 'uid:' + userData.uid + ':topics', req, res, next);
},
], callback);
}
function generateForTag(req, res, next) {
if (parseInt(meta.config['feeds:disableRSS'], 10) === 1) {
return controllers404.send404(req, res);
@ -381,4 +425,3 @@ function sendFeed(feed, res) {
var xml = feed.xml();
res.type('xml').set('Content-Length', Buffer.byteLength(xml)).send(xml);
}

@ -187,8 +187,10 @@ SocketAdmin.config.setMultiple = function (socket, data, callback) {
logger.monitorConfig({ io: index.server }, setting);
}
}
plugins.fireHook('action:config.set', { settings: data });
setImmediate(next);
data.type = 'config-change';
data.uid = socket.uid;
data.ip = socket.ip;
events.log(data, next);
},
], callback);
};
@ -202,7 +204,19 @@ SocketAdmin.settings.get = function (socket, data, callback) {
};
SocketAdmin.settings.set = function (socket, data, callback) {
meta.settings.set(data.hash, data.values, callback);
async.waterfall([
function (next) {
meta.settings.set(data.hash, data.values, next);
},
function (next) {
var eventData = data.values;
eventData.type = 'settings-change';
eventData.uid = socket.uid;
eventData.ip = socket.ip;
eventData.hash = data.hash;
events.log(eventData, next);
},
], callback);
};
SocketAdmin.settings.clearSitemapCache = function (socket, data, callback) {

@ -98,7 +98,7 @@ function setupConfigs() {
nconf.set('secure', urlObject.protocol === 'https:');
nconf.set('use_port', !!urlObject.port);
nconf.set('relative_path', relativePath);
nconf.set('port', urlObject.port || nconf.get('port') || nconf.get('PORT') || (nconf.get('PORT_ENV_VAR') ? nconf.get(nconf.get('PORT_ENV_VAR')) : false) || 4567);
nconf.set('port', urlObject.port || nconf.get('port') || (nconf.get('PORT_ENV_VAR') ? nconf.get(nconf.get('PORT_ENV_VAR')) : false) || 4567);
nconf.set('upload_url', '/assets/uploads');
}

@ -6,6 +6,7 @@ var db = require('../database');
var meta = require('../meta');
var events = require('../events');
var batch = require('../batch');
var utils = require('../utils');
module.exports = function (User) {
User.auth = {};
@ -47,6 +48,29 @@ module.exports = function (User) {
], callback);
};
User.auth.getFeedToken = function (uid, callback) {
if (!uid) {
return callback();
}
var token;
async.waterfall([
function (next) {
User.getUserField(uid, 'rss_token', next);
},
function (_token, next) {
token = _token || utils.generateUUID();
if (!_token) {
User.setUserField(uid, 'rss_token', token, next);
} else {
next();
}
},
function (next) {
next(null, token);
},
], callback);
};
User.auth.clearLoginAttempts = function (uid) {
db.delete('loginAttempts:' + uid);
};

@ -283,7 +283,10 @@ module.exports = function (User) {
},
function (hashedPassword, next) {
async.parallel([
async.apply(User.setUserField, data.uid, 'password', hashedPassword),
async.apply(User.setUserFields, data.uid, {
password: hashedPassword,
rss_token: utils.generateUUID(),
}),
async.apply(User.reset.updateExpiry, data.uid),
async.apply(User.auth.revokeAllSessions, data.uid),
], function (err) {

@ -133,8 +133,8 @@ function setupExpressApp(app, callback) {
app.use(compression());
app.get('/ping', ping);
app.get('/sping', ping);
app.get(relativePath + '/ping', ping);
app.get(relativePath + '/sping', ping);
setupFavicon(app);
@ -231,6 +231,7 @@ function setupAutoLocale(app, callback) {
function listen(callback) {
callback = callback || function () { };
console.log('derp', nconf.get('port'));
var port = parseInt(nconf.get('port'), 10);
var isSocket = isNaN(port);
var socketPath = isSocket ? nconf.get('port') : '';

@ -12,6 +12,7 @@ var groups = require('../src/groups');
var user = require('../src/user');
var meta = require('../src/meta');
var privileges = require('../src/privileges');
var helpers = require('./helpers');
describe('feeds', function () {
var tid;
@ -113,4 +114,81 @@ describe('feeds', function () {
});
});
});
describe('private feeds and tokens', function () {
var jar;
var rssToken;
before(function (done) {
helpers.loginUser('foo', 'barbar', function (err, _jar) {
assert.ifError(err);
jar = _jar;
done();
});
});
it('should load feed if its not private', function (done) {
request(nconf.get('url') + '/category/' + cid + '.rss', { }, function (err, res, body) {
assert.ifError(err);
assert.equal(res.statusCode, 200);
assert(body);
done();
});
});
it('should not allow access if uid or token is missing', function (done) {
privileges.categories.rescind(['read'], cid, 'guests', function (err) {
assert.ifError(err);
async.parallel({
test1: function (next) {
request(nconf.get('url') + '/category/' + cid + '.rss?uid=' + fooUid, { }, next);
},
test2: function (next) {
request(nconf.get('url') + '/category/' + cid + '.rss?token=sometoken', { }, next);
},
}, function (err, results) {
assert.ifError(err);
assert.equal(results.test1[0].statusCode, 200);
assert.equal(results.test2[0].statusCode, 200);
assert(results.test1[0].body.indexOf('Login to your account') !== -1);
assert(results.test2[0].body.indexOf('Login to your account') !== -1);
done();
});
});
});
it('should not allow access if token is wrong', function (done) {
request(nconf.get('url') + '/category/' + cid + '.rss?uid=' + fooUid + '&token=sometoken', { }, function (err, res, body) {
assert.ifError(err);
assert.equal(res.statusCode, 200);
assert(body.indexOf('Login to your account') !== -1);
done();
});
});
it('should allow access if token is correct', function (done) {
request(nconf.get('url') + '/api/category/' + cid, { jar: jar, json: true }, function (err, res, body) {
assert.ifError(err);
rssToken = body.rssFeedUrl.split('token')[1].slice(1);
request(nconf.get('url') + '/category/' + cid + '.rss?uid=' + fooUid + '&token=' + rssToken, { }, function (err, res, body) {
assert.ifError(err);
assert.equal(res.statusCode, 200);
assert(body);
done();
});
});
});
it('should not allow access if token is correct but has no privilege', function (done) {
privileges.categories.rescind(['read'], cid, 'registered-users', function (err) {
assert.ifError(err);
request(nconf.get('url') + '/category/' + cid + '.rss?uid=' + fooUid + '&token=' + rssToken, { }, function (err, res, body) {
assert.ifError(err);
assert.equal(res.statusCode, 200);
assert(body.indexOf('Login to your account') !== -1);
done();
});
});
});
});
});

@ -110,7 +110,7 @@ before(function (done) {
nconf.set('secure', urlObject.protocol === 'https:');
nconf.set('use_port', !!urlObject.port);
nconf.set('relative_path', relativePath);
nconf.set('port', urlObject.port || nconf.get('port') || nconf.get('PORT') || (nconf.get('PORT_ENV_VAR') ? nconf.get(nconf.get('PORT_ENV_VAR')) : false) || 4567);
nconf.set('port', urlObject.port || nconf.get('port') || (nconf.get('PORT_ENV_VAR') ? nconf.get(nconf.get('PORT_ENV_VAR')) : false) || 4567);
nconf.set('upload_path', path.join(nconf.get('base_dir'), nconf.get('upload_path')));
nconf.set('core_templates_path', path.join(__dirname, '../../src/views'));

Loading…
Cancel
Save