Merge branch 'master' of github.com:designcreateplay/NodeBB

v1.18.x
Julian Lam 11 years ago
commit 8f67252547

@ -18,6 +18,7 @@ trans.fr = public/language/fr/category.json
trans.he = public/language/he/category.json
trans.hu = public/language/hu/category.json
trans.it = public/language/it/category.json
trans.ja = public/language/ja/category.json
trans.lt = public/language/lt/category.json
trans.ms = public/language/ms/category.json
trans.nb = public/language/nb/category.json
@ -30,6 +31,7 @@ trans.sk = public/language/sk/category.json
trans.sv = public/language/sv/category.json
trans.th = public/language/th/category.json
trans.tr = public/language/tr/category.json
trans.vi = public/language/vi/category.json
trans.zh_CN = public/language/zh_CN/category.json
trans.zh_TW = public/language/zh_TW/category.json
type = KEYVALUEJSON
@ -51,6 +53,7 @@ trans.fr = public/language/fr/login.json
trans.he = public/language/he/login.json
trans.hu = public/language/hu/login.json
trans.it = public/language/it/login.json
trans.ja = public/language/ja/login.json
trans.lt = public/language/lt/login.json
trans.ms = public/language/ms/login.json
trans.nb = public/language/nb/login.json
@ -63,6 +66,7 @@ trans.sk = public/language/sk/login.json
trans.sv = public/language/sv/login.json
trans.th = public/language/th/login.json
trans.tr = public/language/tr/login.json
trans.vi = public/language/vi/login.json
trans.zh_CN = public/language/zh_CN/login.json
trans.zh_TW = public/language/zh_TW/login.json
type = KEYVALUEJSON
@ -83,6 +87,7 @@ trans.fr = public/language/fr/recent.json
trans.he = public/language/he/recent.json
trans.hu = public/language/hu/recent.json
trans.it = public/language/it/recent.json
trans.ja = public/language/ja/recent.json
trans.lt = public/language/lt/recent.json
trans.ms = public/language/ms/recent.json
trans.nb = public/language/nb/recent.json
@ -95,6 +100,7 @@ trans.sk = public/language/sk/recent.json
trans.sv = public/language/sv/recent.json
trans.th = public/language/th/recent.json
trans.tr = public/language/tr/recent.json
trans.vi = public/language/vi/recent.json
trans.zh_CN = public/language/zh_CN/recent.json
trans.zh_TW = public/language/zh_TW/recent.json
type = KEYVALUEJSON
@ -115,6 +121,7 @@ trans.fr = public/language/fr/unread.json
trans.he = public/language/he/unread.json
trans.hu = public/language/hu/unread.json
trans.it = public/language/it/unread.json
trans.ja = public/language/ja/unread.json
trans.lt = public/language/lt/unread.json
trans.ms = public/language/ms/unread.json
trans.nb = public/language/nb/unread.json
@ -127,6 +134,7 @@ trans.sk = public/language/sk/unread.json
trans.sv = public/language/sv/unread.json
trans.th = public/language/th/unread.json
trans.tr = public/language/tr/unread.json
trans.vi = public/language/vi/unread.json
trans.zh_CN = public/language/zh_CN/unread.json
trans.zh_TW = public/language/zh_TW/unread.json
type = KEYVALUEJSON
@ -147,6 +155,7 @@ trans.fr = public/language/fr/modules.json
trans.he = public/language/he/modules.json
trans.hu = public/language/hu/modules.json
trans.it = public/language/it/modules.json
trans.ja = public/language/ja/modules.json
trans.lt = public/language/lt/modules.json
trans.ms = public/language/ms/modules.json
trans.nb = public/language/nb/modules.json
@ -159,6 +168,7 @@ trans.sk = public/language/sk/modules.json
trans.sv = public/language/sv/modules.json
trans.th = public/language/th/modules.json
trans.tr = public/language/tr/modules.json
trans.vi = public/language/vi/modules.json
trans.zh_CN = public/language/zh_CN/modules.json
trans.zh_TW = public/language/zh_TW/modules.json
type = KEYVALUEJSON
@ -179,6 +189,7 @@ trans.fr = public/language/fr/register.json
trans.he = public/language/he/register.json
trans.hu = public/language/hu/register.json
trans.it = public/language/it/register.json
trans.ja = public/language/ja/register.json
trans.lt = public/language/lt/register.json
trans.ms = public/language/ms/register.json
trans.nb = public/language/nb/register.json
@ -191,6 +202,7 @@ trans.sk = public/language/sk/register.json
trans.sv = public/language/sv/register.json
trans.th = public/language/th/register.json
trans.tr = public/language/tr/register.json
trans.vi = public/language/vi/register.json
trans.zh_CN = public/language/zh_CN/register.json
trans.zh_TW = public/language/zh_TW/register.json
type = KEYVALUEJSON
@ -211,6 +223,7 @@ trans.fr = public/language/fr/user.json
trans.he = public/language/he/user.json
trans.hu = public/language/hu/user.json
trans.it = public/language/it/user.json
trans.ja = public/language/ja/user.json
trans.lt = public/language/lt/user.json
trans.ms = public/language/ms/user.json
trans.nb = public/language/nb/user.json
@ -223,6 +236,7 @@ trans.sk = public/language/sk/user.json
trans.sv = public/language/sv/user.json
trans.th = public/language/th/user.json
trans.tr = public/language/tr/user.json
trans.vi = public/language/vi/user.json
trans.zh_CN = public/language/zh_CN/user.json
trans.zh_TW = public/language/zh_TW/user.json
type = KEYVALUEJSON
@ -243,6 +257,7 @@ trans.fr = public/language/fr/global.json
trans.he = public/language/he/global.json
trans.hu = public/language/hu/global.json
trans.it = public/language/it/global.json
trans.ja = public/language/ja/global.json
trans.lt = public/language/lt/global.json
trans.ms = public/language/ms/global.json
trans.nb = public/language/nb/global.json
@ -255,6 +270,7 @@ trans.sk = public/language/sk/global.json
trans.sv = public/language/sv/global.json
trans.th = public/language/th/global.json
trans.tr = public/language/tr/global.json
trans.vi = public/language/vi/global.json
trans.zh_CN = public/language/zh_CN/global.json
trans.zh_TW = public/language/zh_TW/global.json
type = KEYVALUEJSON
@ -275,6 +291,7 @@ trans.fr = public/language/fr/notifications.json
trans.he = public/language/he/notifications.json
trans.hu = public/language/hu/notifications.json
trans.it = public/language/it/notifications.json
trans.ja = public/language/ja/notifications.json
trans.lt = public/language/lt/notifications.json
trans.ms = public/language/ms/notifications.json
trans.nb = public/language/nb/notifications.json
@ -287,6 +304,7 @@ trans.sk = public/language/sk/notifications.json
trans.sv = public/language/sv/notifications.json
trans.th = public/language/th/notifications.json
trans.tr = public/language/tr/notifications.json
trans.vi = public/language/vi/notifications.json
trans.zh_CN = public/language/zh_CN/notifications.json
trans.zh_TW = public/language/zh_TW/notifications.json
type = KEYVALUEJSON
@ -307,6 +325,7 @@ trans.fr = public/language/fr/reset_password.json
trans.he = public/language/he/reset_password.json
trans.hu = public/language/hu/reset_password.json
trans.it = public/language/it/reset_password.json
trans.ja = public/language/ja/reset_password.json
trans.lt = public/language/lt/reset_password.json
trans.ms = public/language/ms/reset_password.json
trans.nb = public/language/nb/reset_password.json
@ -319,6 +338,7 @@ trans.sk = public/language/sk/reset_password.json
trans.sv = public/language/sv/reset_password.json
trans.th = public/language/th/reset_password.json
trans.tr = public/language/tr/reset_password.json
trans.vi = public/language/vi/reset_password.json
trans.zh_CN = public/language/zh_CN/reset_password.json
trans.zh_TW = public/language/zh_TW/reset_password.json
type = KEYVALUEJSON
@ -339,6 +359,7 @@ trans.fr = public/language/fr/users.json
trans.he = public/language/he/users.json
trans.hu = public/language/hu/users.json
trans.it = public/language/it/users.json
trans.ja = public/language/ja/users.json
trans.lt = public/language/lt/users.json
trans.ms = public/language/ms/users.json
trans.nb = public/language/nb/users.json
@ -351,6 +372,7 @@ trans.sk = public/language/sk/users.json
trans.sv = public/language/sv/users.json
trans.th = public/language/th/users.json
trans.tr = public/language/tr/users.json
trans.vi = public/language/vi/users.json
trans.zh_CN = public/language/zh_CN/users.json
trans.zh_TW = public/language/zh_TW/users.json
type = KEYVALUEJSON
@ -371,6 +393,7 @@ trans.fr = public/language/fr/language.json
trans.he = public/language/he/language.json
trans.hu = public/language/hu/language.json
trans.it = public/language/it/language.json
trans.ja = public/language/ja/language.json
trans.lt = public/language/lt/language.json
trans.ms = public/language/ms/language.json
trans.nb = public/language/nb/language.json
@ -383,6 +406,7 @@ trans.sk = public/language/sk/language.json
trans.sv = public/language/sv/language.json
trans.th = public/language/th/language.json
trans.tr = public/language/tr/language.json
trans.vi = public/language/vi/language.json
trans.zh_CN = public/language/zh_CN/language.json
trans.zh_TW = public/language/zh_TW/language.json
type = KEYVALUEJSON
@ -403,6 +427,7 @@ trans.fr = public/language/fr/pages.json
trans.he = public/language/he/pages.json
trans.hu = public/language/hu/pages.json
trans.it = public/language/it/pages.json
trans.ja = public/language/ja/pages.json
trans.lt = public/language/lt/pages.json
trans.ms = public/language/ms/pages.json
trans.nb = public/language/nb/pages.json
@ -415,6 +440,7 @@ trans.sk = public/language/sk/pages.json
trans.sv = public/language/sv/pages.json
trans.th = public/language/th/pages.json
trans.tr = public/language/tr/pages.json
trans.vi = public/language/vi/pages.json
trans.zh_CN = public/language/zh_CN/pages.json
trans.zh_TW = public/language/zh_TW/pages.json
type = KEYVALUEJSON
@ -435,6 +461,7 @@ trans.fr = public/language/fr/topic.json
trans.he = public/language/he/topic.json
trans.hu = public/language/hu/topic.json
trans.it = public/language/it/topic.json
trans.ja = public/language/ja/topic.json
trans.lt = public/language/lt/topic.json
trans.ms = public/language/ms/topic.json
trans.nb = public/language/nb/topic.json
@ -447,6 +474,7 @@ trans.sk = public/language/sk/topic.json
trans.sv = public/language/sv/topic.json
trans.th = public/language/th/topic.json
trans.tr = public/language/tr/topic.json
trans.vi = public/language/vi/topic.json
trans.zh_CN = public/language/zh_CN/topic.json
trans.zh_TW = public/language/zh_TW/topic.json
type = KEYVALUEJSON
@ -467,6 +495,7 @@ trans.fr = public/language/fr/success.json
trans.he = public/language/he/success.json
trans.hu = public/language/hu/success.json
trans.it = public/language/it/success.json
trans.ja = public/language/ja/success.json
trans.lt = public/language/lt/success.json
trans.ms = public/language/ms/success.json
trans.nb = public/language/nb/success.json
@ -479,6 +508,7 @@ trans.sk = public/language/sk/success.json
trans.sv = public/language/sv/success.json
trans.th = public/language/th/success.json
trans.tr = public/language/tr/success.json
trans.vi = public/language/vi/success.json
trans.zh_CN = public/language/zh_CN/success.json
trans.zh_TW = public/language/zh_TW/success.json
type = KEYVALUEJSON
@ -499,6 +529,7 @@ trans.fr = public/language/fr/error.json
trans.he = public/language/he/error.json
trans.hu = public/language/hu/error.json
trans.it = public/language/it/error.json
trans.ja = public/language/ja/error.json
trans.lt = public/language/lt/error.json
trans.ms = public/language/ms/error.json
trans.nb = public/language/nb/error.json
@ -511,6 +542,7 @@ trans.sk = public/language/sk/error.json
trans.sv = public/language/sv/error.json
trans.th = public/language/th/error.json
trans.tr = public/language/tr/error.json
trans.vi = public/language/vi/error.json
trans.zh_CN = public/language/zh_CN/error.json
trans.zh_TW = public/language/zh_TW/error.json
type = KEYVALUEJSON

@ -66,7 +66,7 @@
"posted_in_ago_by_guest": "posted in %1 %2 by Guest",
"posted_in_ago_by": "posted in %1 %2 by %3",
"posted_in_ago": "posted in %1",
"posted_in_ago": "posted in %1 %2",
"replied_ago": "replied %1",
"user_posted_ago": "%1 posted %2",

@ -10,6 +10,7 @@
"profile": "Profile",
"posted_by": "Posted by %1",
"posted_by_guest": "Posted by Guest",
"chat": "Chat",
"notify_me": "Be notified of new replies in this topic",
"quote": "Quote",

@ -0,0 +1,7 @@
{
"new_topic_button": "新規スレッド",
"no_topics": "<strong>まだスレッドはありません.</strong><br />一番目のスレッドを書いてみないか?",
"browsing": "閲覧中",
"no_replies": "返事はまだありません",
"share_this_category": "この板を共有"
}

@ -0,0 +1,49 @@
{
"invalid-data": "無効なデータ",
"not-logged-in": "ログインしていていないようです。",
"invalid-cid": "無効な板ID",
"invalid-tid": "無効なスレッドID",
"invalid-pid": "無効なポストID",
"invalid-uid": "無効なユーザーID",
"invalid-username": "無効なユーザー名",
"invalid-email": "無効なメール",
"invalid-title": "無効なタイトル",
"invalid-user-data": "無効なユーザーデータ",
"invalid-password": "無効なパスワード",
"invalid-pagination-value": "無効な改ページ設定値",
"username-taken": "ユーザー名が取られた",
"email-taken": "メールアドレスが使用された",
"user-banned": "ユーザーが停止された",
"no-category": "板が存在しない",
"no-topic": "スレッドが存在しない",
"no-post": "ポストが存在しない",
"no-group": "グループが存在しない",
"no-user": "ユーザーが存在しない",
"no-teaser": "ティーザーが存在しない",
"no-privileges": "このアクションを実行する権限を持っていない。",
"category-disabled": "この板は無効された",
"topic-locked": "スレッドがロックされた",
"still-uploading": "アップロードが完成するまでお待ちください。",
"content-too-short": "ポストに最小 %1 文字の制限があります。",
"title-too-short": "タイトルに最小 %1 文字の制限があります。",
"title-too-long": "タイトルに最大 %1 文字の制限があります。",
"too-many-posts": "%1 秒間で最大 1 ポストの投稿が可能です。",
"file-too-big": "最大ファイルサイズは %1 kbs",
"cant-vote-self-post": "自分のポストに評価することはできません。",
"already-favourited": "このポストはすでにお気に入りに追加されました。",
"already-unfavourited": "このポストはすでにお気に入りから削除されました。",
"cant-ban-other-admins": "ほかの管理者を停止することはできません!",
"invalid-image-type": "無効なユーザータイプ",
"group-name-too-short": "グループ名は短すぎます。",
"group-already-exists": "グループ名はすでに存在しています",
"group-name-change-not-allowed": "グループ名の修正はできません",
"post-already-deleted": "ポストはすでに削除された",
"post-already-restored": "ポストはすでにリストアされた",
"topic-already-deleted": "スレッドはすでに削除された",
"topic-already-restored": "スレッドはすでにリストアされた",
"topic-thumbnails-are-disabled": "スレッドのサムネイルが無効された",
"invalid-file": "無効なファイル",
"uploads-are-disabled": "アップロードが無効された",
"signature-too-long": "署名は最大%1文字までです",
"cant-chat-with-yourself": "自分にチャットすることはできません!"
}

@ -0,0 +1,7 @@
{
"stats.online": "利用者",
"stats.users": "登録",
"stats.topics": "スレッド",
"stats.posts": "ポスト",
"success": "成功"
}

@ -0,0 +1,70 @@
{
"home": "ホーム",
"search": "検索",
"buttons.close": "閉じる",
"403.title": "アクセス拒否",
"403.message": "アクセスが拒否されました.<a href='/login'>ログインしますか</a>",
"404.title": "見つかりません",
"404.message": "アクセスしようとしていたページは存在しません.<a href='/'>ホーム</a>へ",
"500.title": "内部エラー",
"500.message": "何とも言えない障害が発生しているようです.m(_ _)m",
"register": "登録",
"login": "ログイン",
"please_log_in": "ログインください",
"logout": "ログアウト",
"posting_restriction_info": "登録ユーザーのみが投稿可能となります.こちらからログインください。",
"welcome_back": "お帰りなさい",
"you_have_successfully_logged_in": "ログインできました",
"save_changes": "保存する",
"close": "閉じる",
"pagination": "ページ",
"header.admin": "管理",
"header.recent": "最近",
"header.unread": "未読",
"header.popular": "人気",
"header.users": "ユーザー",
"header.chats": "チャット",
"header.notifications": "通知",
"header.search": "検索",
"header.profile": "プロフィール",
"notifications.loading": "通知をロード中",
"chats.loading": "チャットをロード中",
"motd.welcome": "次世代の掲示板システム NodeBB へようこそ!",
"previouspage": "前のページ",
"nextpage": "次のページ",
"alert.success": "成功",
"alert.error": "エラー",
"alert.banned": "停止した",
"alert.banned.message": "このアカウントが停止されました!",
"alert.unfollow": "%1へのフォローを停止しました",
"alert.follow": "%1をフォローしています",
"online": "オンライン",
"users": "ユーザー",
"topics": "スレッド",
"posts": "ポスト",
"views": "閲覧数",
"reputation": "評価",
"read_more": "続きを読む",
"posted_ago_by_guest": "%1にゲストが投稿",
"posted_ago_by": "%1に %2 が投稿",
"posted_ago": "%1に投稿された",
"posted_in_ago_by_guest": "%1に %2 ゲストが投稿",
"posted_in_ago_by": "%1 %2に %3 が投稿",
"posted_in_ago": "%1 に投稿",
"replied_ago": "%1 に返答",
"user_posted_ago": "%1 が%2に投稿",
"guest_posted_ago": "ゲストが%1に投稿",
"last_edited_by_ago": "%1が%2に最終編集",
"norecentposts": "最近のポストはありません",
"norecenttopics": "最近のスレッドはありません",
"recentposts": "最近のポスト",
"recentips": "最近ログインしたIPアドレス",
"away": "不在",
"dnd": "仕事中",
"invisible": "不可視",
"offline": "オフライン",
"email": "メール",
"language": "言語",
"guest": "ゲスト",
"guests": "ゲスト"
}

@ -0,0 +1,5 @@
{
"name": "日本語",
"code": "ja",
"dir": "ltr"
}

@ -0,0 +1,8 @@
{
"username": "ユーザー名 / メール",
"remember_me": "ログイン情報を記憶",
"forgot_password": "パスワードがわからない?",
"alternative_logins": "ほかのログイン方法",
"failed_login_attempt": "ログインに失敗しました.ユーザー名やパスワードをご確認ください。",
"login_successful": "ログインしました!"
}

@ -0,0 +1,7 @@
{
"chat.chatting_with": "<span id=\"chat-with-name\"></span>とチャット",
"chat.placeholder": "メッセージを入力する",
"chat.send": "送信",
"chat.no_active": "チャットはありません。",
"chat.user_typing": "%1 は入力中 ..."
}

@ -0,0 +1,18 @@
{
"title": "通知センター",
"no_notifs": "新しい通知はありません",
"see_all": "すべての通知を確認",
"back_to_home": "NodeBBへ戻る",
"outgoing_link": "外部サイトへのリンク",
"outgoing_link_message": "リービング",
"continue_to": "続き",
"return_to": "戻る ",
"new_notification": "新しい通知",
"you_have_unread_notifications": "未読の通知があります。",
"user_made_post": "<strong>%1</strong>は新しいポストを投稿しました。",
"new_message_from": "<strong>%1</strong>からの新しいメッセージ",
"upvoted_your_post": "<strong>%1</strong>はあなたのポストを評価しました。",
"favourited_your_post": "<strong>%1</strong>はあなたのポストをお気に入りにしました。",
"user_flagged_post": "<strong>%1</strong> ポストを報告しました。",
"user_posted_to": "<strong>%1</strong> は <strong>%2</strong> への返事を作成しました。"
}

@ -0,0 +1,15 @@
{
"home": "ホーム",
"unread": "未読スレッド",
"popular": "人気スレッド",
"recent": "最新スレッド",
"users": "登録したユーザー",
"notifications": "通知",
"user.edit": "編集中 \"%1\"",
"user.following": "%1がフォロー中",
"user.followers": "%1のフォロワー",
"user.posts": "%1が作成したポスト",
"user.topics": "%1が作成したスレッド",
"user.favourites": "%1のお気に入りポスト",
"user.settings": "ユーザー設定"
}

@ -0,0 +1,7 @@
{
"title": "最近の更新",
"day": "最近 1 日",
"week": "最近 1 週",
"month": "最近 1 ヶ月",
"no_recent_topics": "最近のスレッドはありません。"
}

@ -0,0 +1,18 @@
{
"register": "登録",
"help.email": "デフォルト設定ではメールアドレスは公開されません。",
"help.username_restrictions": "%1 から %2 文字までのユニークなユーザー名.Twitter の @<span id='yourUsername'>username</span> 方式でメンションすることができます。",
"help.minimum_password_length": "パスワードには最小 %1 文字が必要です。",
"email_address": "メールアドレス",
"email_address_placeholder": "メールアドレスを入力ください",
"username": "ユーザー名",
"username_placeholder": "ユーザー名を入力してください",
"password": "パスワード",
"password_placeholder": "パスワードを入力してください",
"confirm_password": "パスワード再入力",
"confirm_password_placeholder": "パスワード再入力してください",
"register_now_button": "今すぐ登録する",
"alternative_registration": "ほかの登録方法",
"terms_of_use": "利用規約",
"agree_to_terms_of_use": "利用規約に同意する"
}

@ -0,0 +1,14 @@
{
"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": "パスワードリセットのメールを送信しました",
"invalid_email": "このメールアドレスは存在しません"
}

@ -0,0 +1,6 @@
{
"success": "成功",
"topic-post": "成功に投稿しました。",
"authentication-successful": "認証が成功しました",
"settings-saved": "設定を保存しました。"
}

@ -0,0 +1,93 @@
{
"topic": "スレッド",
"topic_id": "スレッド ID",
"topic_id_placeholder": "スレッド ID を入力してください",
"no_topics_found": "スレッドが見つかりません!",
"no_posts_found": "ポストはありません!",
"post_is_deleted": "このポストが削除されます!",
"profile": "プロフィール",
"posted_by": "%1 のポスト",
"chat": "チャット",
"notify_me": "このスレッドに新しいポストが投稿された際に通知する",
"quote": "引用",
"reply": "返答",
"edit": "編集",
"delete": "削除",
"restore": "リストア",
"move": "移動",
"fork": "フォーク",
"banned": "停止した",
"link": "リンク",
"share": "シェア",
"tools": "ツール",
"flag": "フラグ",
"bookmark_instructions": "こちらをクリックして前の場所に戻ります。",
"flag_title": "リポートする",
"flag_confirm": "本当にこのポストをリポートするか?",
"flag_success": "このポストをリポートしました。",
"deleted_message": "このスレッドは削除されました。管理者しか見れません。",
"following_topic.message": "このスレッドが更新された際に通知を受け取ります。",
"not_following_topic.message": "このスレッドの更新通知を停止しました。",
"login_to_subscribe": "このスレッドを購読するためにログインが必要です。",
"markAsUnreadForAll.success": "すべてのスレッドを未読にしました。",
"watch": "ウオッチ",
"watch.title": "新しいポストの通知を受ける",
"share_this_post": "ポストを共有",
"thread_tools.title": "スレッドツール",
"thread_tools.markAsUnreadForAll": "未読にする",
"thread_tools.pin": "スレッドを最上部に固定",
"thread_tools.unpin": "スレッドの固定を解除",
"thread_tools.lock": "スレッドをロック",
"thread_tools.unlock": "スレッドをアンロック",
"thread_tools.move": "スレッドを移動",
"thread_tools.move_all": "すべてを移動",
"thread_tools.fork": "スレッドをフォーク",
"thread_tools.delete": "スレッドを削除",
"thread_tools.delete_confirm": "本当にスレッドを削除しますか?",
"thread_tools.restore": "スレッドをリストア",
"thread_tools.restore_confirm": "本当にスレッドをリストアしますか?",
"topic_lock_success": "スレッドはロックされました。",
"topic_unlock_success": "スレッドはアンロックされました。",
"topic_pin_success": "スレッドは上部に固定されました。",
"topic_unpin_success": "スレッドの固定を取り消しました。",
"topic_move_success": "このスレッドを %1 に移動しました。",
"post_delete_confirm": "本当にポストを削除しますか?",
"post_restore_confirm": "本当にポストをリストアしますか?",
"post_delete_error": "ポストを削除できませんでした!",
"post_restore_error": "ポストをリストアできませんでした!",
"load_categories": "板をローディング中...",
"disabled_categories_note": "使用不可の板はグレーに表示されます。",
"confirm_move": "移動",
"confirm_fork": "フォーク",
"favourite": "お気に入り",
"favourites": "お気に入り",
"favourites.has_no_favourites": "お気に入りはまだありません!",
"loading_more_posts": "もっと見る",
"move_topic": "スレッドを移動",
"move_post": "ポストを移動",
"post_moved": "ポストを移動しました!",
"fork_topic": "スレッドをフォーク",
"topic_will_be_moved_to": "スレッドはこちらのカテゴリへ移動",
"fork_topic_instruction": "フォークしたいポストをクリックして",
"fork_no_pids": "ポストが選択されていません!",
"fork_success": "スレッドをフォークできました!",
"composer.title_placeholder": "スレッドのタイトルを入力して...",
"composer.write": "作成する",
"composer.preview": "プレビュー",
"composer.help": "ヘルプ",
"composer.discard": "破棄する",
"composer.submit": "保存する",
"composer.replying_to": "%1へ返答中",
"composer.new_topic": "新規スレッド",
"composer.uploading": "アップロード中...",
"composer.thumb_url_label": "スレッドのサムネイルのURLを入力して",
"composer.thumb_title": "スレッドにサムネイルを追加",
"composer.thumb_url_placeholder": "http://example.com/thumb.png",
"composer.thumb_file_label": "またはファイルをアップロード",
"composer.thumb_remove": "入力をクリア",
"composer.drag_and_drop_images": "こちらへ画像をドラッグ&ドロップ",
"composer.upload_instructions": "ドラッグ&ドロップで画像をアップロードすることができます。",
"more_users_and_guests": "%1 more user(s) and %2 guest(s)",
"more_users": "%1 more user(s)",
"more_guests": "%1 more guest(s)"
}

@ -0,0 +1,9 @@
{
"title": "未読",
"no_unread_topics": "未読のスレッドがあります。",
"load_more": "もっと見る",
"mark_as_read": "既読にする",
"selected": "選択済み",
"all": "すべて",
"topics_marked_as_read.success": "すべてのスレッドを既読にしました。"
}

@ -0,0 +1,62 @@
{
"banned": "停止された",
"offline": "オフライン",
"username": "ユーザー名",
"email": "メール",
"fullname": "フルネーム",
"website": "ウェブサイト",
"location": "ロケーション",
"age": "年齢",
"joined": "参加",
"lastonline": "最後オンライン",
"profile": "プロフィール",
"profile_views": "閲覧数",
"reputation": "評価",
"favourites": "お気に入り",
"followers": "フォロワー",
"following": "フォロー中",
"signature": "署名",
"gravatar": "Gravatar",
"birthday": "誕生日",
"chat": "チャット",
"follow": "フォロー",
"unfollow": "フォローしない",
"profile_update_success": "プロフィールを更新しました!",
"change_picture": "画像を変更",
"edit": "編集",
"uploaded_picture": "アップロード済みの画像",
"upload_new_picture": "新しい画像をアップロード",
"current_password": "現在のパスワード",
"change_password": "パスワードを変更",
"change_password_error": "無効のパスワード!",
"change_password_error_wrong_current": "現在のパスワードは正しくありません!",
"change_password_error_length": "パスワードは短い過ぎです!",
"change_password_error_match": "パスワードは一致しません!",
"change_password_error_privileges": "パスワードを更新する権限はありません。",
"change_password_success": "パスワードを更新しました!",
"confirm_password": "パスワードを再入力",
"password": "パスワード",
"username_taken_workaround": "このユーザー名はすでに使用されています。いまのユーザー名は <strong>%1</strong> です。",
"upload_picture": "画像をアップロード",
"upload_a_picture": "画像をアップロード",
"image_spec": "PNG、JPGかGIFフォーマットの画像をアップロードしてください。",
"max": "max。",
"settings": "設定",
"show_email": "メールアドレスを表示",
"digest_label": "ダイジェストを購読",
"digest_description": "この掲示板のアップデートを受信する",
"digest_off": "オフ",
"digest_daily": "デイリー",
"digest_weekly": "ウィークリー",
"digest_monthly": "マンスリー",
"has_no_follower": "フォロワーはまだいません :(",
"follows_no_one": "フォロー中のユーザーはまだいません :(",
"has_no_posts": "まだポストを投稿したことありません。",
"has_no_topics": "まだスレッドを作成したありません。",
"email_hidden": "メールアドレスを非表示",
"hidden": "非表示",
"paginate_description": "スクロールでのページ自動ロードはしない",
"topics_per_page": "ページ毎のスレッド数",
"posts_per_page": "ページ毎のポスト数",
"notification_sounds": "通知が来たとき音を流す"
}

@ -0,0 +1,8 @@
{
"latest_users": "最新ユーザー",
"top_posters": "最も投稿したユーザー",
"most_reputation": "最も評価されたユーザー",
"search": "検索",
"enter_username": "検索するユーザー名を入力してください",
"load_more": "もっと表示"
}

@ -0,0 +1,7 @@
{
"new_topic_button": "Chủ đề mới",
"no_topics": "<strong>Không có bài viết trong danh mục.</strong><br />Hãy đăng một bài viết mới?",
"browsing": "đang duyệt",
"no_replies": "Chưa có ai bình luận",
"share_this_category": "Chia sẻ phần mục này"
}

@ -0,0 +1,49 @@
{
"invalid-data": "Dữ liệu không hợp lệ",
"not-logged-in": "Bạn không được đăng nhập.",
"invalid-cid": "Danh mục ID không hợp lệ",
"invalid-tid": "ID chủ đề không hợp lệ",
"invalid-pid": "ID bài viết không hợp lệ",
"invalid-uid": "ID tài khoản không hợp lệ",
"invalid-username": "Tên đăng nhập không hợp lệ",
"invalid-email": "Email không hợp lệ",
"invalid-title": "Tiêu đề không hợp lệ",
"invalid-user-data": "Dữ liệu tài khoản không hợp lệ",
"invalid-password": "Mật khẩu không hợp lệ",
"invalid-pagination-value": "Số trang không hợp lệ",
"username-taken": "Tên đăng nhập đã tồn tại",
"email-taken": "Email đã tồn tại",
"user-banned": "Tài khoản bị ban",
"no-category": "Phần mục không tồn tại",
"no-topic": "Chủ đề không tồn tại",
"no-post": "Bài viết không tồn tại",
"no-group": "Nhóm không tồn tại",
"no-user": "Tài khoản không tồn tại",
"no-teaser": "Teaser không tồn tại",
"no-privileges": "Bạn không đủ quyền cho hành động này",
"category-disabled": "Danh mục bị disabled",
"topic-locked": "Chủ đề bị khóa",
"still-uploading": "Vui lòng chờ upload",
"content-too-short": "Vui lòng nhập dài hơn. Ít nhất %1 ký tự",
"title-too-short": "Yêu cầu nhập tiêu đề dài hơn. Ít nhất %1 ký tự",
"title-too-long": "Yêu cầu tiêu đề ngắn hơn. Không dài quá %1 ký tự",
"too-many-posts": "Bạn chỉ có thể gửi bài viết %1 giây nữa",
"file-too-big": "Kích thước file tối đa %1kb",
"cant-vote-self-post": "Bạn không thể vote cho chính bài viết của bạn",
"already-favourited": "Bạn đã bấm yêu thích cho bài viết này",
"already-unfavourited": "Bạn đã từ bỏ yêu thích cho bài viết này",
"cant-ban-other-admins": "Bạn không thể ban được các admin khác",
"invalid-image-type": "Kiểu hình ảnh không hợp lệ",
"group-name-too-short": "Tên nhóm quá ngắn",
"group-already-exists": "Nhóm đã tồn tại",
"group-name-change-not-allowed": "Không cho phép đổi tên nhóm",
"post-already-deleted": "Bài viết đã được xóa rồi",
"post-already-restored": "Bài viết đã được phục hồi rồi",
"topic-already-deleted": "Chủ đề đã bị xóa rồi",
"topic-already-restored": "Chủ đề đã được phục hồi rồi",
"topic-thumbnails-are-disabled": "Thumbnails cho chủ đề đã bị tắt",
"invalid-file": "File không hợp lệ",
"uploads-are-disabled": "Đã khóa lựa chọn tải lên",
"signature-too-long": "Chứ ký không được dài quá %1 ký tự",
"cant-chat-with-yourself": "Bạn không thể chat với chính bạn!"
}

@ -0,0 +1,70 @@
{
"home": "Trang chủ",
"search": "Tìm kiếm",
"buttons.close": "Đóng lại",
"403.title": "Từ chối truy cập",
"403.message": "Có vẻ như bạn đang cố vào một trang mà bạn không có quyền truy cập. Bạn nên thử đăng nhập để truy cập ",
"404.title": "Không tìm thấy",
"404.message": "Có vẻ bạn đang cố vào một trang không tồn tại. Hãy trở lại trang chủ",
"500.title": "Lỗi nội bộ",
"500.message": "Úi chà! Có vẻ như có trục trặc rồi!",
"register": "Đăng ký",
"login": "Đăng nhập",
"please_log_in": "Xin hãy đăng nhập",
"logout": "Đăng xuất",
"posting_restriction_info": "Hiện giờ chỉ có các thành viên mới được quyền gửi bài viết, hãy nhấn vào đây để đăng nhập",
"welcome_back": "Chào mừng bạn quay lại",
"you_have_successfully_logged_in": "Bạn đã đăng nhập thành công",
"save_changes": "Lưu thay đổi",
"close": "Đóng lại",
"pagination": "Số trang",
"header.admin": "Quản trị viên",
"header.recent": "Gần đây",
"header.unread": "Chưa đọc",
"header.popular": "Nổi bật",
"header.users": "Số người dùng",
"header.chats": "Phần Chat",
"header.notifications": "Thông báo",
"header.search": "Tìm kiếm",
"header.profile": "Hồ sơ",
"notifications.loading": "Đang tải Thông báo",
"chats.loading": "Đang tải phần Chat",
"motd.welcome": "Chào mừng bạn tới NodeBB, Platform của tương lai",
"previouspage": "Trang trước",
"nextpage": "Trang kế tiếp",
"alert.success": "Thành công",
"alert.error": "Lỗi",
"alert.banned": "Bị ban",
"alert.banned.message": "Bạn đã bị ban, giờ bạn sẽ đăng xuất",
"alert.unfollow": "Bạn đã không còn theo dõi %1!",
"alert.follow": "Bạn giờ đang theo dõi %1!",
"online": "Đang online",
"users": "Số người dùng",
"topics": "Số Chủ đề",
"posts": "Số bài viết",
"views": "Số lượt view",
"reputation": "Độ uy tín",
"read_more": "Đọc thêm",
"posted_ago_by_guest": "Đã viết %1 bởi Khách",
"posted_ago_by": "Đã viết %1 bởi %2",
"posted_ago": "Đã viết %1",
"posted_in_ago_by_guest": "Đã viết trong %1 %2 bởi Khách",
"posted_in_ago_by": "Đã viết trong %1 %2 bởi %3",
"posted_in_ago": "Đã viết trong %1",
"replied_ago": "Đã trả lời %1",
"user_posted_ago": "%1 đã viết %2",
"guest_posted_ago": "Khách đã viết %1",
"last_edited_by_ago": "Được chỉnh sửa lần cuối bởi %1 %2",
"norecentposts": "Không có bài viết nào gần đây",
"norecenttopics": "Không có chủ đề gần đây",
"recentposts": "Số bài viết gần đây",
"recentips": "Các IP vừa mới đăng nhập",
"away": "Đang away",
"dnd": "Không được quấy rầy",
"invisible": "Ẩn",
"offline": "Đang offline",
"email": "Email",
"language": "Ngôn ngữ",
"guest": "Khách",
"guests": "Số khách"
}

@ -0,0 +1,5 @@
{
"name": "Tiếng Việt",
"code": "vi",
"dir": "ltr"
}

@ -0,0 +1,8 @@
{
"username": "Tên người dùng / Email",
"remember_me": "Ghi nhớ tôi?",
"forgot_password": "Quên mật khẩu?",
"alternative_logins": "Đăng nhập bằng tên khác",
"failed_login_attempt": "Đăng nhập thất bại, xin hãy thử lại",
"login_successful": "Bạn đã đăng nhập thành công!"
}

@ -0,0 +1,7 @@
{
"chat.chatting_with": "Chat với <span id=\"chat-with-name\"></span>",
"chat.placeholder": "đánh đoạn tin nhắn chat ở đây, nhấn phím enter để gửi đi",
"chat.send": "Gửi đi",
"chat.no_active": "Bạn hiện giờ không có cuộc chat nào",
"chat.user_typing": "%1b đang gõ"
}

@ -0,0 +1,18 @@
{
"title": "Thông báo",
"no_notifs": "Bạn không có thông báo nào mới",
"see_all": "Xem tất cả thông báo",
"back_to_home": "Quay lại NodeBB",
"outgoing_link": "Liên kết ngoài",
"outgoing_link_message": "Bạn giờ đang thoát",
"continue_to": "Tiếp tục đến",
"return_to": "Trở về",
"new_notification": "Thông báo mới",
"you_have_unread_notifications": "Bạn có thông báo chưa đọc",
"user_made_post": "<strong>%1</strong> đã viết bài mới",
"new_message_from": "Tin nhắn mới từ <strong>%1</strong>",
"upvoted_your_post": "<strong>%1</strong> đã hủy vote cho bài viết của bạn",
"favourited_your_post": "<strong>%1</strong> thích bài viết của bạn",
"user_flagged_post": "<strong>%1</strong> đã flag một bài viết",
"user_posted_to": "<strong>%1</strong> đã trả lời <strong>%2</strong>"
}

@ -0,0 +1,15 @@
{
"home": "Trang chủ",
"unread": "Chủ đề chưa đọc",
"popular": "Các chủ đề nổi bật",
"recent": "Chủ đề gần đây",
"users": "Số người dùng đã đăng ký",
"notifications": "Thông báo",
"user.edit": "Chỉnh sửa \"%1\"",
"user.following": "Người mà %1 theo dõi",
"user.followers": "Người đang theo dõi %1",
"user.posts": "Các bài được %1 viết",
"user.topics": "Các chủ đề được %1 tạo",
"user.favourites": "Các bài gửi yêu thích của %1",
"user.settings": "Thiết lập cho người dùng"
}

@ -0,0 +1,7 @@
{
"title": "Mới nhất",
"day": "Ngày",
"week": "Tuần",
"month": "Tháng",
"no_recent_topics": "Không có chủ đề nào gần đây"
}

@ -0,0 +1,18 @@
{
"register": "Đăng ký",
"help.email": "Theo mặc định, Email của bạn sẽ ở dạng ẩn và public sẽ không thấy được",
"help.username_restrictions": "Một tên truy cập duy nhất có từ %1 đến %2 ký tự. Những người khác có thể nhắc đến bạn bằng @<span id='yourUsername'>tên truy cập</span>.",
"help.minimum_password_length": "Mật khẩu của bạn phải có ít nhất %1 ký tự",
"email_address": "Địa chỉ Email",
"email_address_placeholder": "Nhập địa chỉ Email",
"username": "Tên truy cập",
"username_placeholder": "Nhập tên truy cập",
"password": "Mật khẩu",
"password_placeholder": "Nhập mật khẩu",
"confirm_password": "Xác nhận mật khẩu",
"confirm_password_placeholder": "Xác nhận mật khẩu",
"register_now_button": "Đăng ký ngay",
"alternative_registration": "Đăng ký tài khoản khác",
"terms_of_use": "Điều khoản sử dụng",
"agree_to_terms_of_use": "Tôi đồng ý với các điều khoản sử dụng"
}

@ -0,0 +1,14 @@
{
"reset_password": "Thiết lập lại mật khẩu",
"update_password": "Cập nhật mật khẩu",
"password_changed.title": "Mật khẩu đã được thay đổi",
"password_changed.message": "<p>Mật khẩu đã được thiết lập lại thành công, xin hãy <a href=\"/login\">đăng nhập lại</a>.",
"wrong_reset_code.title": "Mã thiết lập lại không đúng",
"wrong_reset_code.message": "Mã thiết lập lại không đúng. Xin hãy thử lại, hoặc <a href=\"/reset\">yêu cầu một mã thiết lập lại khác</a>.",
"new_password": "Mật khẩu mới",
"repeat_password": "Xác nhận lại mật khẩu",
"enter_email": "Xin hãy nhập <strong>địa chỉ email</strong> của bạn và chúng tôi sẽ gửi một email hướng dẫn cách thiết lập lại tài khoản cho bạn",
"enter_email_address": "Nhập địa chỉ Email",
"password_reset_sent": "Đã gửi mật khẩu được thiết lập lại",
"invalid_email": "Email không đúng / Email không tồn tại!"
}

@ -0,0 +1,6 @@
{
"success": "Thành công",
"topic-post": "Bạn đã gửi bài thành công",
"authentication-successful": "Xác thực thành công",
"settings-saved": "Đã lưu thiết lập"
}

@ -0,0 +1,93 @@
{
"topic": "Chủ đề",
"topic_id": "ID của chủ đề",
"topic_id_placeholder": "Nhập ID của chủ đề",
"no_topics_found": "Không tìm thấy chủ đề nào!",
"no_posts_found": "Không tìm thấy bài gửi nào",
"post_is_deleted": "Bài gửi này đã bị xóa!",
"profile": "Hồ sơ",
"posted_by": "Được viết bởi %1",
"chat": "Chat",
"notify_me": "Được thông báo khi có trả lời mới trong chủ đề này",
"quote": "Trích dẫn",
"reply": "Trả lời",
"edit": "Chỉnh sửa",
"delete": "Xóa",
"restore": "Phục hồi",
"move": "Chuyển đi",
"fork": "Fork",
"banned": "bị ban",
"link": "đường dẫn",
"share": "Chia sẻ",
"tools": "Công cụ",
"flag": "Flag",
"bookmark_instructions": "Bấm vào đây để quay về hoặc đóng lại để hủy",
"flag_title": "Flag bài viết này để chỉnh sửa",
"flag_confirm": "Bạn có chắc là muốn flag bài viết này không?",
"flag_success": "Chủ đề này đã được flag để chỉnh sửa",
"deleted_message": "Thread này đã bị xóa. Chỉ người dùng có quyền quản lý thread mới xem được.",
"following_topic.message": "Từ giờ bạn sẽ nhận được thông báo khi có ai đó gửi bài viết trong chủ đề này",
"not_following_topic.message": "Bạn sẽ không còn nhận được thông báo từ chủ đề này",
"login_to_subscribe": "Xin hãy đăng ký hoặc đăng nhập để theo dõi topic này",
"markAsUnreadForAll.success": "Chủ đề đã được đánh dấu là chưa đọc toàn bộ",
"watch": "Xem",
"watch.title": "Được thông báo khi có trả lời mới trong chủ đề này",
"share_this_post": "Chia sẻ bài viết này",
"thread_tools.title": "Công cụ cho Thread",
"thread_tools.markAsUnreadForAll": "Đánh dấu chưa đọc",
"thread_tools.pin": "Pin chủ đề",
"thread_tools.unpin": "Bỏ pin chủ đề",
"thread_tools.lock": "Khóa chủ đề",
"thread_tools.unlock": "Mở khóa chủ đề",
"thread_tools.move": "Chuyển chủ đề",
"thread_tools.move_all": "Chuyển tất cả",
"thread_tools.fork": "Fork chủ đề",
"thread_tools.delete": "Xóa chủ đề",
"thread_tools.delete_confirm": "Bạn có chắc là muốn hủy thread này không?",
"thread_tools.restore": "Phục hồi chủ đề",
"thread_tools.restore_confirm": "Bạn có chắc là muốn phục hồi thread này không",
"topic_lock_success": "Đã khóa thành công chủ đề.",
"topic_unlock_success": "Đã mở khóa thành công chủ đề.",
"topic_pin_success": "Đã pin chủ đề thành công",
"topic_unpin_success": "Đã bỏ pin chủ đề thành công",
"topic_move_success": "Đã chuyển thành công chủ đề này sang %1",
"post_delete_confirm": "Bạn có chắc là muốn xóa bài gửi này không?",
"post_restore_confirm": "Bạn có chắc là muốn phục hồi bài gửi này không?",
"post_delete_error": "Không thể xóa bài gửi này!",
"post_restore_error": "Không thể phục hồi bài gửi này!",
"load_categories": "Đang tải các phần mục",
"disabled_categories_note": "Các phần mục bị khóa đã được đánh xám",
"confirm_move": "Chuyển",
"confirm_fork": "Fork",
"favourite": "Yêu thích",
"favourites": "Đang yêu thích",
"favourites.has_no_favourites": "Bạn đang không có yêu thích nào. Hãy yêu thích một vài bài viết để thấy được chúng tại đây!",
"loading_more_posts": "Tải thêm các bài gửi khác",
"move_topic": "Chuyển chủ đề",
"move_post": "Chuyển bài gửi",
"post_moved": "Đã chuyển bài gửi!",
"fork_topic": "Fork chủ đề",
"topic_will_be_moved_to": "Chủ đề này sẽ được chuyển tới phần mục",
"fork_topic_instruction": "Nhấp vào bài gửi mà bạn muốn fork",
"fork_no_pids": "Chưa chọn bài gửi nào!",
"fork_success": "Đã fork chủ đề thành công!",
"composer.title_placeholder": "Nhập tiêu đề cho chủ đề của bạn tại đây...",
"composer.write": "Viết",
"composer.preview": "Xem trước",
"composer.help": "Trợ giúp",
"composer.discard": "Loại bỏ",
"composer.submit": "Gửi",
"composer.replying_to": "Đang trả lời %1",
"composer.new_topic": "Chủ đề mới",
"composer.uploading": "đang tải lên...",
"composer.thumb_url_label": "Dán một thumbnail URL cho chủ đề",
"composer.thumb_title": "Thêm một thumbnail cho chủ đề này",
"composer.thumb_url_placeholder": "http://example.com/thumb.png",
"composer.thumb_file_label": "Hoặc tải lên một tệp",
"composer.thumb_remove": "Xóa toàn bộ",
"composer.drag_and_drop_images": "Kéo và thả hình ảnh tại đây",
"composer.upload_instructions": "Tải lên hình ảnh bằng cách kéo và thả",
"more_users_and_guests": "%1 người dùng và %2 khách nữa",
"more_users": "%1 người dùng nữa",
"more_guests": "%1 khách nữa"
}

@ -0,0 +1,9 @@
{
"title": "Chưa đọc",
"no_unread_topics": "Không có chủ đề chưa được đọc",
"load_more": "Tải thêm",
"mark_as_read": "Đánh dấu đã đọc",
"selected": "Đã chọn",
"all": "Tất cả",
"topics_marked_as_read.success": "Chủ đề được đánh dấu đã đọc"
}

@ -0,0 +1,62 @@
{
"banned": "Bị ban",
"offline": "Offline",
"username": "Tên truy cập",
"email": "Email",
"fullname": "Tên đầy đủ",
"website": "Website",
"location": "Địa điểm",
"age": "Tuổi",
"joined": "Đã gia nhập",
"lastonline": "Online lần cuối vào",
"profile": "Hồ sơ",
"profile_views": "Khung hiển thị hồ sơ",
"reputation": "Mức uy tín",
"favourites": "Yêu thích",
"followers": "Số người theo dõi",
"following": "Đang theo dõi",
"signature": "Chữ ký",
"gravatar": "Gavatar",
"birthday": "Ngày sinh ",
"chat": "Chat",
"follow": "Theo dõi",
"unfollow": "Hủy theo dõi",
"profile_update_success": "Hồ sơ đã được cập nhật thành công",
"change_picture": "Thay đổi hình ảnh",
"edit": "Chỉnh sửa",
"uploaded_picture": "Ảnh đã tải lên",
"upload_new_picture": "Tải lên ảnh mới",
"current_password": "Mật khẩu hiện tại",
"change_password": "Thay đổi mật khẩu",
"change_password_error": "Mật khẩu không đúng!",
"change_password_error_wrong_current": "Mật khẩu hiện tại của bạn không đúng",
"change_password_error_length": "Mật khẩu quá ngắn!",
"change_password_error_match": "Mật khẩu phải khớp!",
"change_password_error_privileges": "Bạn không có quyền thay đổi mật khẩu này",
"change_password_success": "Mật khẩu của bạn đã được cập nhật",
"confirm_password": "Xác nhận mật khẩu",
"password": "Mật khẩu",
"username_taken_workaround": "Tên truy cập này đã tồn tại, vì vậy chúng tôi đã sửa đổi nó một chút. Tên truy cập của bạn giờ là <strong>%1</strong>",
"upload_picture": "Tải lên hình ảnh",
"upload_a_picture": "Tải lên một hình ảnh",
"image_spec": "Bạn chỉ có thể tải lên được các file PNG, JPG hoặc GIF",
"max": "tối đa",
"settings": "Thiết lập",
"show_email": "Hiện Email của tôi",
"digest_label": "Đăng ký để digest",
"digest_description": "Đăng ký để được forum gửi cập nhật tới email (thông báo và chủ đề mới) trong một khoảng thời gian lặp lại nhất định ",
"digest_off": "Off",
"digest_daily": "Hàng ngày",
"digest_weekly": "Hàng tuần",
"digest_monthly": "Hàng tháng",
"has_no_follower": "Người dùng này hiện chưa có ai theo dõi :(",
"follows_no_one": "Người dùng này hiện chưa theo dõi ai :(",
"has_no_posts": "Người dùng này chưa viết bài nào",
"has_no_topics": "Người dùng này chưa tạo một chủ đề nào",
"email_hidden": "Ẩn Email",
"hidden": "Đã ẩn",
"paginate_description": "Phân trang cho chủ đề và bài viết thay vì cuộn liên tục",
"topics_per_page": "Số chủ đề trong một trang",
"posts_per_page": "Số bài viết trong một trang",
"notification_sounds": "Xuất hiện âm thanh khi bạn nhận được một thông báo"
}

@ -0,0 +1,8 @@
{
"latest_users": "Những người dùng mới nhất",
"top_posters": "Những người viết bài nhiều nhất",
"most_reputation": "Uy tín nhất",
"search": "Tìm kiếm",
"enter_username": "Gõ tên người dùng để tìm kiếm",
"load_more": "Tải thêm"
}

@ -48,7 +48,7 @@ define(function() {
btn.html('<i class="fa fa-download"></i> Install');
}
btn.toggleClass('btn-warning', status.installed).toggleClass('btn-success', !status.installed)
btn.toggleClass('btn-danger', status.installed).toggleClass('btn-success', !status.installed)
.attr('disabled', false);
activateBtn.toggleClass('hide', !status.installed);

@ -61,21 +61,7 @@ define(['uploader', 'sounds'], function(uploader, sounds) {
}
});
$('#uploadLogoBtn').on('click', function() {
uploader.open(RELATIVE_PATH + '/admin/uploadlogo', {}, 0, function(image) {
$('#logoUrl').val(image);
});
uploader.hideAlerts();
});
$('#uploadFaviconBtn').on('click', function() {
uploader.open(RELATIVE_PATH + '/admin/uploadfavicon', {}, 0, function(icon) {
$('#faviconUrl').val(icon);
});
uploader.hideAlerts();
});
handleUploads();
$('#settings-tab a').click(function (e) {
e.preventDefault();
@ -88,6 +74,19 @@ define(['uploader', 'sounds'], function(uploader, sounds) {
}
};
function handleUploads() {
$('#content input[data-action="upload"]').each(function() {
var uploadBtn = $(this);
uploadBtn.on('click', function() {
uploader.open(uploadBtn.attr('data-route'), {}, 0, function(image) {
$('#' + uploadBtn.attr('data-target')).val(image);
});
uploader.hideAlerts();
});
});
}
Settings.remove = function(key) {
socket.emit('admin.config.remove', key);
};

@ -169,11 +169,14 @@ define(['composer', 'forum/pagination', 'share', 'navigator', 'forum/categoryToo
}
}
Category.onNewTopic = function(data) {
$(window).trigger('filter:categories.new_topic', data);
Category.onNewTopic = function(topic) {
$(window).trigger('filter:categories.new_topic', topic);
ajaxify.loadTemplate('category', function(categoryTemplate) {
var html = templates.parse(templates.getBlock(categoryTemplate, 'topics'), {topics: [data]});
var html = templates.parse(templates.getBlock(categoryTemplate, 'topics'), {
privileges: {editable: !!$('.thread-tools').length},
topics: [topic]
});
translator.translate(html, function(translatedHTML) {
var topic = $(translatedHTML),

@ -243,8 +243,21 @@ define(['forum/pagination', 'forum/topic/threadTools', 'forum/topic/postTools',
socket.on('user.isOnline', function(err, data) {
app.populateOnlineUsers();
updateActiveUsers(data);
});
function updateActiveUsers(data) {
var activeEl = $('.thread_active_users');
var user = activeEl.find('a[data-uid="'+ data.uid + '"]');
if (user.length && !data.online) {
user.parent().remove();
} else if(!user.length && data.online) {
user = createUserIcon(data.uid, data.picture, data.userslug, data.username);
activeEl.append(user);
}
}
socket.on('event:voted', function(data) {
updatePostVotesAndUserReputation(data);
});

@ -953,7 +953,12 @@ define(['taskbar'], function(taskbar) {
content: bodyEl.val(),
topic_thumb: thumbEl.val() || '',
category_id: postData.cid
}, done);
}, function(err, topic) {
done(err);
if (!err) {
ajaxify.go('topic/' + topic.slug);
}
});
} else if (parseInt(postData.tid, 10) > 0) {
socket.emit('posts.reply', {
tid: postData.tid,

@ -4,21 +4,38 @@
define(function() {
var TopicSelect = {};
var lastSelected;
TopicSelect.init = function(onSelect) {
$('#topics-container').on('click', '.select', function() {
$('#topics-container').on('selectstart', function() {
return false;
});
$('#topics-container').on('click', '.select', function(ev) {
var select = $(this);
var isChecked = !select.hasClass('fa-square-o');
select.toggleClass('fa-check-square-o', !isChecked);
select.toggleClass('fa-square-o', isChecked);
select.parents('.category-item').toggleClass('selected', !isChecked);
if (ev.shiftKey) {
selectRange($(this).parents('.category-item').attr('data-tid'));
lastSelected = select;
return false;
}
var isSelected = select.hasClass('fa-check-square-o');
toggleSelect(select, !isSelected);
lastSelected = select;
if (typeof onSelect === 'function') {
onSelect();
}
});
};
function toggleSelect(select, isSelected) {
select.toggleClass('fa-check-square-o', isSelected);
select.toggleClass('fa-square-o', !isSelected);
select.parents('.category-item').toggleClass('selected', isSelected);
}
TopicSelect.getSelectedTids = function() {
var tids = [];
$('#topics-container .category-item.selected').each(function() {
@ -32,5 +49,35 @@ define(function() {
$('#topics-container .select').toggleClass('fa-check-square-o', false).toggleClass('fa-square-o', true);
};
function selectRange(clickedTid) {
if(!lastSelected) {
lastSelected = $('.category-item[data-tid]').first().find('.select');
}
var isClickedSelected = $('.category-item[data-tid="' + clickedTid + '"]').hasClass('selected');
var clickedIndex = getIndex(clickedTid);
var lastIndex = getIndex(lastSelected.parents('.category-item[data-tid]').attr('data-tid'));
selectIndexRange(clickedIndex, lastIndex, !isClickedSelected);
}
function selectIndexRange(start, end, isSelected) {
if (start > end) {
var tmp = start;
start = end;
end = tmp;
}
for(var i=start; i<=end; ++i) {
var topic = $('.category-item[data-tid]').eq(i);
toggleSelect(topic.find('.select'), isSelected);
}
}
function getIndex(tid) {
return $('.category-item[data-tid="' + tid + '"]').index('.category-item');
}
return TopicSelect;
});

@ -317,8 +317,6 @@ var db = require('./database'),
if(parseInt(topicData.pinned, 10) === 0) {
db.sortedSetAdd('categories:' + cid + ':tid', postData.timestamp, postData.tid);
}
Categories.addActiveUser(cid, postData.uid, postData.timestamp);
});
};

@ -1,83 +1,31 @@
'use strict';
var async = require('async'),
db = require('./../database'),
posts = require('./../posts'),
topics = require('./../topics');
module.exports = function(Categories) {
Categories.isUserActiveIn = function(cid, uid, callback) {
db.getSortedSetRange('uid:' + uid + ':posts', 0, -1, function(err, pids) {
if (err) {
return callback(err);
}
var index = 0,
active = false;
async.whilst(
function() {
return active === false && index < pids.length;
},
function(callback) {
posts.getCidByPid(pids[index], function(err, postCid) {
if (err) {
return callback(err);
}
if (postCid === cid) {
active = true;
}
Categories.getActiveUsers = function(cid, callback) {
db.getSortedSetRevRange('categories:recent_posts:cid:' + cid, 0, 99, function(err, pids) {
var keys = pids.map(function(pid) {
return 'post:' + pid;
});
++index;
callback();
});
},
function(err) {
callback(err, active);
db.getObjectsFields(keys, ['uid'], function(err, users) {
if (err) {
return callback(err);
}
);
});
};
Categories.addActiveUser = function(cid, uid, timestamp) {
if(parseInt(uid, 10)) {
db.sortedSetAdd('cid:' + cid + ':active_users', timestamp, uid);
}
};
Categories.removeActiveUser = function(cid, uid, callback) {
db.sortedSetRemove('cid:' + cid + ':active_users', uid, callback);
};
var uids = users.map(function(user) {
return user.uid;
}).filter(function(value, index, array) {
return parseInt(value, 10) !== 0 && array.indexOf(value) === index;
}).slice(0, 24);
Categories.getActiveUsers = function(cid, callback) {
db.getSortedSetRevRange('cid:' + cid + ':active_users', 0, 23, callback);
};
Categories.moveActiveUsers = function(tid, oldCid, cid) {
function updateUser(uid, timestamp) {
Categories.addActiveUser(cid, uid, timestamp);
Categories.isUserActiveIn(oldCid, uid, function(err, active) {
if (!err && !active) {
Categories.removeActiveUser(oldCid, uid);
}
callback(null, uids);
});
}
topics.getTopicField(tid, 'timestamp', function(err, timestamp) {
if(!err) {
topics.getUids(tid, function(err, uids) {
if (!err && uids) {
for (var i = 0; i < uids.length; ++i) {
updateUser(uids[i], timestamp);
}
}
});
}
});
};
};

@ -2,6 +2,8 @@
'use strict';
var async = require('async'),
winston = require('winston'),
db = require('./../database'),
posts = require('./../posts'),
topics = require('./../topics'),
@ -23,26 +25,40 @@ module.exports = function(Categories) {
};
Categories.moveRecentReplies = function(tid, oldCid, cid) {
function movePost(pid, next) {
posts.getPostField(pid, 'timestamp', function(err, timestamp) {
if(err) {
return next(err);
function movePost(postData, next) {
async.parallel([
function(next) {
db.sortedSetRemove('categories:recent_posts:cid:' + oldCid, postData.pid, next);
},
function(next) {
db.sortedSetAdd('categories:recent_posts:cid:' + cid, postData.timestamp, postData.pid, next);
}
db.sortedSetRemove('categories:recent_posts:cid:' + oldCid, pid);
db.sortedSetAdd('categories:recent_posts:cid:' + cid, timestamp, pid);
next();
});
], next);
}
topics.getPids(tid, function(err, pids) {
if(!err && pids) {
async.each(pids, movePost, function(err) {
if (err) {
return winston.error(err.message);
}
if (pids && !pids.length) {
return;
}
var keys = pids.map(function(pid) {
return 'post:' + pid;
});
db.getObjectsFields(keys, ['pid', 'timestamp'], function(err, postData) {
if (err) {
return winston.error(err.message);
}
async.each(postData, movePost, function(err) {
if (err) {
winston.error(err.message);
}
});
}
});
});
};
};

@ -87,7 +87,20 @@ uploadsController.uploadLogo = function(req, res, next) {
if (validateUpload(res, req, allowedTypes)) {
var filename = 'site-logo' + path.extname(req.files.userPhoto.name);
uploadsController.uploadImage(filename, req, res);
}
}
};
uploadsController.uploadGravatarDefault = function(req, res, next) {
var allowedTypes = ['image/png', 'image/jpeg', 'image/pjpeg', 'image/jpg', 'image/gif'],
er;
if (validateUpload(res, req, allowedTypes)) {
var filename = 'gravatar-default' + path.extname(req.files.userPhoto.name);
uploadsController.uploadImage(filename, req, res);
}
};
module.exports = uploadsController;

@ -484,27 +484,60 @@ var fs = require('fs'),
return callback(null, []);
}
async.map(plugins, function(plugin, next) {
var pluginMap = {};
for(var i=0; i<plugins.length; ++i) {
plugins[i].id = plugins[i].name;
plugins[i].installed = false;
plugins[i].active = false;
pluginMap[plugins[i].name] = plugins[i];
}
Plugins.showInstalled(function(err, installedPlugins) {
if (err) {
return callback(err);
}
plugin.id = plugin.name;
async.each(installedPlugins, function(plugin, next) {
async.parallel({
active: function(next) {
Plugins.isActive(plugin.id, next);
},
installed: function(next) {
Plugins.isInstalled(plugin.id, next);
}
}, function(err, results) {
pluginMap[plugin.id] = pluginMap[plugin.id] || {};
pluginMap[plugin.id].id = pluginMap[plugin.id].id || plugin.id;
pluginMap[plugin.id].description = plugin.description;
pluginMap[plugin.id].url = plugin.url;
pluginMap[plugin.id].installed = true;
Plugins.isActive(plugin.id, function(err, active) {
if (err) {
return next(err);
}
pluginMap[plugin.id].active = active;
next();
});
}, function(err) {
if (err) {
return next(err);
return callback(err);
}
var pluginArray = [];
for (var key in pluginMap) {
if (pluginMap.hasOwnProperty(key)) {
pluginArray.push(pluginMap[key]);
}
}
plugin.active = results.active;
plugin.installed = results.installed;
next(null, plugin);
});
}, callback);
pluginArray.sort(function(a, b) {
if(a.installed && !b.installed) {
return -1;
} else if(!a.installed && b.installed) {
return 1;
}
return 0;
});
callback(null, pluginArray);
});
});
});
};
@ -536,7 +569,7 @@ var fs = require('fs'),
}
callback(stats.isDirectory());
})
});
}, function(plugins){
next(null, plugins);
});
@ -556,7 +589,7 @@ var fs = require('fs'),
try {
var config = JSON.parse(configJSON);
} catch (err) {
winston.warn("Plugin: " + file + " is corrupted or invalid. Please check plugin.json for errors.")
winston.warn("Plugin: " + file + " is corrupted or invalid. Please check plugin.json for errors.");
return next(err, null);
}

@ -212,7 +212,7 @@ var winston = require('winston'),
}
function updateTopicTimestamp(tid, callback) {
threadTools.getLatestUndeletedPid(tid, function(err, pid) {
topics.getLatestUndeletedPid(tid, function(err, pid) {
if(err || !pid) {
return callback(err);
}

@ -237,7 +237,7 @@ var db = require('./database'),
reputation: userData.reputation || 0,
postcount: userData.postcount || 0,
banned: parseInt(userData.banned, 10) === 1,
picture: userData.picture || gravatar.url('', {}, true)
picture: userData.picture || user.createGravatarURLFromEmail('')
};
for (var info in customUserInfo) {

@ -57,6 +57,7 @@ function apiRoutes(app, middleware, controllers) {
app.post('/admin/category/uploadpicture', middleware.authenticate, controllers.admin.uploads.uploadCategoryPicture);
app.post('/admin/uploadfavicon', middleware.authenticate, controllers.admin.uploads.uploadFavicon);
app.post('/admin/uploadlogo', middleware.authenticate, controllers.admin.uploads.uploadLogo);
app.post('/admin/uploadgravatardefault', middleware.authenticate, controllers.admin.uploads.uploadGravatarDefault);
}
function miscRoutes(app, middleware, controllers) {

@ -225,6 +225,22 @@ Sockets.getUserSockets = function(uid) {
/* Helpers */
Sockets.reqFromSocket = function(socket) {
var headers = socket.handshake.headers,
host = headers.host,
referer = headers.referer;
return {
ip: headers['x-forwarded-for'] || (socket.handshake.address || {}).address,
host: host,
protocol: headers.secure ? 'https' : 'http',
secure: !!headers.secure,
url: referer,
path: referer.substr(referer.indexOf(host) + host.length),
headers: headers
};
};
Sockets.isUserOnline = isUserOnline;
function isUserOnline(uid) {
return Sockets.getUserSockets(uid).length > 0;
@ -263,8 +279,6 @@ function updateRoomBrowsingText(roomName) {
var uids = getUidsInRoom(),
anonymousCount = getAnonymousCount();
user.getMultipleUserFields(uids, ['uid', 'username', 'userslug', 'picture', 'status'], function(err, users) {
if(!err) {
users = users.filter(function(user) {

@ -67,9 +67,7 @@ SocketMeta.updateHeader = function(socket, data, callback) {
uid: 0,
username: '[[global:guest]]',
email: '',
picture: gravatar.url('', {
s: '24'
}, true),
picture: user.createGravatarURLFromEmail(''),
config: {
allowGuestSearching: meta.config.allowGuestSearching
}

@ -162,7 +162,7 @@ SocketModules.chats.send = function(socket, data, callback) {
return callback(new Error('[[error:invalid-data]]'));
}
var touid = data.touid;
var touid = parseInt(data.touid, 10);
if (touid === socket.uid || socket.uid === 0) {
return;
}

@ -15,6 +15,8 @@ var async = require('async'),
SocketPosts = {};
SocketPosts.reply = function(socket, data, callback) {
if (!socket.uid && !parseInt(meta.config.allowGuestPosting, 10)) {
@ -26,6 +28,7 @@ SocketPosts.reply = function(socket, data, callback) {
}
data.uid = socket.uid;
data.req = websockets.reqFromSocket(socket);
topics.reply(data, function(err, postData) {
if(err) {

@ -5,7 +5,7 @@ var topics = require('../topics'),
categories = require('../categories'),
threadTools = require('../threadTools'),
categoryTools = require('../categoryTools'),
index = require('./index'),
websockets = require('./index'),
user = require('../user'),
db = require('./../database'),
meta = require('./../meta'),
@ -25,26 +25,33 @@ SocketTopics.post = function(socket, data, callback) {
return callback(new Error('[[error:not-logged-in]]'));
}
topics.post({uid: socket.uid, title: data.title, content: data.content, cid: data.category_id, thumb: data.topic_thumb}, function(err, result) {
topics.post({
uid: socket.uid,
title: data.title,
content: data.content,
cid: data.category_id,
thumb: data.topic_thumb,
req: websockets.reqFromSocket(socket)
}, function(err, result) {
if(err) {
return callback(err);
}
if (result) {
index.server.sockets.in('category_' + data.category_id).emit('event:new_topic', result.topicData);
index.server.sockets.in('recent_posts').emit('event:new_topic', result.topicData);
index.server.sockets.in('home').emit('event:new_topic', result.topicData);
index.server.sockets.in('home').emit('event:new_post', {
websockets.server.sockets.in('category_' + data.category_id).emit('event:new_topic', result.topicData);
websockets.server.sockets.in('recent_posts').emit('event:new_topic', result.topicData);
websockets.server.sockets.in('home').emit('event:new_topic', result.topicData);
websockets.server.sockets.in('home').emit('event:new_post', {
posts: result.postData
});
index.server.sockets.in('user/' + socket.uid).emit('event:new_post', {
websockets.server.sockets.in('user/' + socket.uid).emit('event:new_post', {
posts: result.postData
});
module.parent.exports.emitTopicPostStats();
callback();
callback(null, result.topicData);
}
});
};
@ -253,11 +260,11 @@ SocketTopics.move = function(socket, data, callback) {
return next(err);
}
index.server.sockets.in('topic_' + tid).emit('event:topic_moved', {
websockets.server.sockets.in('topic_' + tid).emit('event:topic_moved', {
tid: tid
});
index.server.sockets.in('category_' + oldCid).emit('event:topic_moved', {
websockets.server.sockets.in('category_' + oldCid).emit('event:topic_moved', {
tid: tid
});
@ -300,7 +307,7 @@ SocketTopics.moveAll = function(socket, data, callback) {
};
SocketTopics.followCheck = function(socket, tid, callback) {
threadTools.isFollowing(tid, socket.uid, callback);
topics.isFollowing(tid, socket.uid, callback);
};
SocketTopics.follow = function(socket, tid, callback) {

@ -74,6 +74,12 @@ var winston = require('winston'),
}
topics[isDelete ? 'delete' : 'restore'](tid, function(err) {
function emitTo(room) {
websockets.in(room).emit(isDelete ? 'event:topic_deleted' : 'event:topic_restored', {
tid: tid,
isDelete: isDelete
});
}
if(err) {
return callback(err);
}
@ -86,15 +92,8 @@ var winston = require('winston'),
websockets.emitTopicPostStats();
websockets.in('topic_' + tid).emit(isDelete ? 'event:topic_deleted' : 'event:topic_restored', {
tid: tid,
isDelete: isDelete
});
websockets.in('category_' + topicData.cid).emit(isDelete ? 'event:topic_deleted' : 'event:topic_restored', {
tid: tid,
isDelete: isDelete
});
emitTo('topic_' + tid);
emitTo('category_' + topicData.cid);
callback(null, {
tid: tid
@ -113,21 +112,21 @@ var winston = require('winston'),
function toggleLock(tid, uid, lock, callback) {
topics.getTopicField(tid, 'cid', function(err, cid) {
function emitTo(room) {
websockets.in(room).emit(lock ? 'event:topic_locked' : 'event:topic_unlocked', {
tid: tid,
isLocked: lock
});
}
if (err) {
return callback(err);
}
topics.setTopicField(tid, 'locked', lock ? 1 : 0);
websockets.in('topic_' + tid).emit(lock ? 'event:topic_locked' : 'event:topic_unlocked', {
tid: tid,
isLocked: lock
});
websockets.in('category_' + cid).emit(lock ? 'event:topic_locked' : 'event:topic_unlocked', {
tid: tid,
isLocked: lock
});
emitTo('topic_' + tid);
emitTo('category_' + cid);
if (typeof callback === 'function') {
callback(null, {
@ -148,6 +147,13 @@ var winston = require('winston'),
function togglePin(tid, uid, pin, callback) {
topics.getTopicField(tid, 'cid', function(err, cid) {
function emitTo(room) {
websockets.in(room).emit(pin ? 'event:topic_pinned' : 'event:topic_unpinned', {
tid: tid,
isPinned: pin
});
}
if (err) {
return callback(err);
}
@ -157,15 +163,8 @@ var winston = require('winston'),
db.sortedSetAdd('categories:' + topicData.cid + ':tid', pin ? Math.pow(2, 53) : topicData.lastposttime, tid);
});
websockets.in('topic_' + tid).emit(pin ? 'event:topic_pinned' : 'event:topic_unpinned', {
tid: tid,
isPinned: pin
});
websockets.in('category_' + cid).emit(pin ? 'event:topic_pinned' : 'event:topic_unpinned', {
tid: tid,
isPinned: pin
});
emitTo('topic_' + tid);
emitTo('category_' + cid);
if (typeof callback === 'function') {
callback(null, {
@ -201,25 +200,21 @@ var winston = require('winston'),
categories.incrementCategoryFieldBy(cid, 'topic_count', 1);
}
categories.moveActiveUsers(tid, oldCid, cid);
categories.moveRecentReplies(tid, oldCid, cid);
topics.setTopicField(tid, 'cid', cid, callback);
});
};
ThreadTools.isFollowing = function(tid, uid, callback) {
db.isSetMember('tid:' + tid + ':followers', uid, callback);
};
ThreadTools.toggleFollow = function(tid, uid, callback) {
ThreadTools.isFollowing(tid, uid, function(err, following) {
topics.isFollowing(tid, uid, function(err, following) {
if(err) {
return callback(err);
}
db[following?'setRemove':'setAdd']('tid:' + tid + ':followers', uid, function(err, success) {
db[following ? 'setRemove' : 'setAdd']('tid:' + tid + ':followers', uid, function(err, success) {
if (callback) {
if(err) {
return callback(err);
@ -231,97 +226,4 @@ var winston = require('winston'),
});
};
ThreadTools.getFollowers = function(tid, callback) {
db.getSetMembers('tid:' + tid + ':followers', function(err, followers) {
if(err) {
return callback(err);
}
if(followers) {
followers = followers.map(function(follower) {
return parseInt(follower, 10);
});
}
callback(null, followers);
});
};
ThreadTools.notifyFollowers = function(tid, pid, exceptUid) {
async.parallel([
function(next) {
topics.getTopicFields(tid, ['title', 'slug'], function(err, topicData) {
if(err) {
return next(err);
}
user.getUserField(exceptUid, 'username', function(err, username) {
if(err) {
return next(err);
}
notifications.create({
text: '[[notifications:user_posted_to, ' + username + ', ' + topicData.title + ']]',
path: nconf.get('relative_path') + '/topic/' + topicData.slug + '#' + pid,
uniqueId: 'topic:' + tid,
from: exceptUid
}, function(nid) {
next(null, nid);
});
});
});
},
function(next) {
ThreadTools.getFollowers(tid, function(err, followers) {
if(err) {
return next(err);
}
exceptUid = parseInt(exceptUid, 10);
if (followers.indexOf(exceptUid) !== -1) {
followers.splice(followers.indexOf(exceptUid), 1);
}
next(null, followers);
});
}
], function(err, results) {
if (!err && results[1].length) {
notifications.push(results[0], results[1]);
}
});
};
ThreadTools.getLatestUndeletedPost = function(tid, callback) {
ThreadTools.getLatestUndeletedPid(tid, function(err, pid) {
if(err) {
return callback(err);
}
posts.getPostData(pid, callback);
});
};
ThreadTools.getLatestUndeletedPid = function(tid, callback) {
db.getSortedSetRevRange('tid:' + tid + ':posts', 0, -1, function(err, pids) {
if(err) {
return callback(err);
}
if (!pids.length) {
return callback(null, null);
}
async.detectSeries(pids, function(pid, next) {
posts.getPostField(pid, 'deleted', function(err, deleted) {
next(parseInt(deleted, 10) === 0);
});
}, function(pid) {
if (pid) {
callback(null, pid);
} else {
callback(null, null);
}
});
});
};
}(exports));

@ -20,6 +20,7 @@ var async = require('async'),
require('./topics/recent')(Topics);
require('./topics/fork')(Topics);
require('./topics/posts')(Topics);
require('./topics/follow')(Topics);
Topics.getTopicData = function(tid, callback) {
@ -304,7 +305,7 @@ var async = require('async'),
};
Topics.getTeaser = function(tid, callback) {
threadTools.getLatestUndeletedPid(tid, function(err, pid) {
Topics.getLatestUndeletedPid(tid, function(err, pid) {
if (err) {
return callback(err);
}

@ -83,6 +83,16 @@ module.exports = function(Topics) {
}
async.waterfall([
function(next) {
plugins.fireHook('filter:topic.post', data, function(err, filteredData) {
if (err) {
return next(err);
}
content = filteredData.content || data.content;
next();
});
},
function(next) {
categoryTools.exists(cid, next);
},
@ -105,7 +115,7 @@ module.exports = function(Topics) {
Topics.create({uid: uid, title: title, cid: cid, thumb: thumb}, next);
},
function(tid, next) {
Topics.reply({uid:uid, tid:tid, content:content}, next);
Topics.reply({uid:uid, tid:tid, content:content, req: data.req}, next);
},
function(postData, next) {
threadTools.toggleFollow(postData.tid, uid);
@ -140,6 +150,16 @@ module.exports = function(Topics) {
postData;
async.waterfall([
function(next) {
plugins.fireHook('filter:topic.reply', data, function(err, filteredData) {
if (err) {
return next(err);
}
content = filteredData.content || data.content;
next();
});
},
function(next) {
threadTools.exists(tid, next);
},
@ -180,7 +200,8 @@ module.exports = function(Topics) {
},
function(data, next) {
postData = data;
threadTools.notifyFollowers(tid, postData.pid, uid);
Topics.notifyFollowers(tid, postData.pid, uid);
user.notifications.sendPostNotificationToFollowers(uid, tid, postData.pid);

@ -0,0 +1,62 @@
'use strict';
var async = require('async'),
nconf = require('nconf'),
db = require('../database'),
user = require('../user'),
notifications = require('../notifications');
module.exports = function(Topics) {
Topics.isFollowing = function(tid, uid, callback) {
db.isSetMember('tid:' + tid + ':followers', uid, callback);
};
Topics.getFollowers = function(tid, callback) {
db.getSetMembers('tid:' + tid + ':followers', callback);
};
Topics.notifyFollowers = function(tid, pid, exceptUid) {
async.parallel({
nid: function(next) {
Topics.getTopicFields(tid, ['title', 'slug'], function(err, topicData) {
if(err) {
return next(err);
}
user.getUserField(exceptUid, 'username', function(err, username) {
if(err) {
return next(err);
}
notifications.create({
text: '[[notifications:user_posted_to, ' + username + ', ' + topicData.title + ']]',
path: nconf.get('relative_path') + '/topic/' + topicData.slug + '#' + pid,
uniqueId: 'topic:' + tid,
from: exceptUid
}, function(nid) {
next(null, nid);
});
});
});
},
followers: function(next) {
Topics.getFollowers(tid, next);
}
}, function(err, results) {
if (!err && results.followers.length) {
var index = results.followers.indexOf(exceptUid.toString());
if (index !== -1) {
results.followers.splice(index, 1);
}
notifications.push(results.nid, results.followers);
}
});
};
};

@ -68,6 +68,7 @@ module.exports = function(Topics) {
postData[i].votes = postData[i].votes || 0;
postData[i].display_moderator_tools = parseInt(uid, 10) !== 0 && results.privileges[i].editable;
postData[i].display_move_tools = results.privileges[i].move;
postData[i].selfPost = parseInt(uid, 10) === parseInt(postData[i].uid, 10);
if(postData[i].deleted && !results.privileges[i].view_deleted) {
postData[i].content = '[[topic:post_is_deleted]]';
@ -79,6 +80,36 @@ module.exports = function(Topics) {
});
};
Topics.getLatestUndeletedPost = function(tid, callback) {
Topics.getLatestUndeletedPid(tid, function(err, pid) {
if(err) {
return callback(err);
}
posts.getPostData(pid, callback);
});
};
Topics.getLatestUndeletedPid = function(tid, callback) {
db.getSortedSetRevRange('tid:' + tid + ':posts', 0, -1, function(err, pids) {
if(err) {
return callback(err);
}
if (!pids || !pids.length) {
return callback(null, null);
}
async.detectSeries(pids, function(pid, next) {
posts.getPostField(pid, 'deleted', function(err, deleted) {
next(parseInt(deleted, 10) === 0);
});
}, function(pid) {
callback(null, pid ? pid : null);
});
});
};
Topics.addPostToTopic = function(tid, pid, timestamp, callback) {
db.sortedSetAdd('tid:' + tid + ':posts', timestamp, pid, callback);
};

@ -210,15 +210,15 @@ var bcrypt = require('bcryptjs'),
};
User.createGravatarURLFromEmail = function(email) {
var options = {
size: '128',
default: 'identicon',
default: meta.config.customGravatarDefaultImage || meta.config.defaultGravatarImage || '',
rating: 'pg'
};
if (!email) {
email = '';
options.forcedefault = 'y';
}
return gravatar.url(email, options, true);
@ -294,7 +294,7 @@ var bcrypt = require('bcryptjs'),
callback(null, {
username: data.username || '[[global:guest]]',
userslug: data.userslug || '',
picture: data.picture || gravatar.url('', {}, true)
picture: data.picture || User.createGravatarURLFromEmail('')
});
});
};
@ -354,25 +354,24 @@ var bcrypt = require('bcryptjs'),
};
User.isOnline = function(uid, callback) {
User.getUserField(uid, 'status', function(err, status) {
User.getUserFields(uid, ['username', 'userslug', 'picture', 'status'] , function(err, data) {
if(err) {
return callback(err);
}
var online = require('./socket.io').isUserOnline(uid);
status = online ? (status || 'online') : 'offline';
data.status = online ? (data.status || 'online') : 'offline';
if(status === 'offline') {
if(data.status === 'offline') {
online = false;
}
callback(null, {
online: online,
uid: uid,
timestamp: Date.now(),
status: status
});
data.online = online;
data.uid = uid;
data.timestamp = Date.now();
callback(null, data);
});
};

@ -266,9 +266,6 @@ module.exports = function(User) {
function(next) {
db.delete('uid:' + uid + ':downvote', next);
},
function(next) {
deleteUserFromCategoryActiveUsers(uid, next);
},
function(next) {
deleteUserFromFollowers(uid, next);
},
@ -298,18 +295,6 @@ module.exports = function(User) {
});
}
function deleteUserFromCategoryActiveUsers(uid, callback) {
db.getSortedSetRange('categories:cid', 0, -1, function(err, cids) {
if (err) {
return callback(err);
}
async.each(cids, function(cid, next) {
categories.removeActiveUser(cid, uid, next);
}, callback);
});
}
function deleteUserFromFollowers(uid, callback) {
db.getSetMembers('followers:' + uid, function(err, uids) {
if (err) {

Loading…
Cancel
Save