diff --git a/.tx/config b/.tx/config index 600be44023..eb73e33399 100644 --- a/.tx/config +++ b/.tx/config @@ -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 \ No newline at end of file diff --git a/public/language/en_GB/global.json b/public/language/en_GB/global.json index e306ed49ba..6ed0e914b1 100644 --- a/public/language/en_GB/global.json +++ b/public/language/en_GB/global.json @@ -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", diff --git a/public/language/en_GB/topic.json b/public/language/en_GB/topic.json index 7669a23fe0..2af258a89d 100644 --- a/public/language/en_GB/topic.json +++ b/public/language/en_GB/topic.json @@ -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", diff --git a/public/language/ja/category.json b/public/language/ja/category.json new file mode 100644 index 0000000000..0a4892d07f --- /dev/null +++ b/public/language/ja/category.json @@ -0,0 +1,7 @@ +{ + "new_topic_button": "新規スレッド", + "no_topics": "まだスレッドはありません.
一番目のスレッドを書いてみないか?", + "browsing": "閲覧中", + "no_replies": "返事はまだありません", + "share_this_category": "この板を共有" +} diff --git a/public/language/ja/error.json b/public/language/ja/error.json new file mode 100644 index 0000000000..facf88fcf5 --- /dev/null +++ b/public/language/ja/error.json @@ -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": "自分にチャットすることはできません!" +} diff --git a/public/language/ja/footer.json b/public/language/ja/footer.json new file mode 100644 index 0000000000..41437643ee --- /dev/null +++ b/public/language/ja/footer.json @@ -0,0 +1,7 @@ +{ + "stats.online": "利用者", + "stats.users": "登録", + "stats.topics": "スレッド", + "stats.posts": "ポスト", + "success": "成功" +} diff --git a/public/language/ja/global.json b/public/language/ja/global.json new file mode 100644 index 0000000000..35269d5d82 --- /dev/null +++ b/public/language/ja/global.json @@ -0,0 +1,70 @@ +{ + "home": "ホーム", + "search": "検索", + "buttons.close": "閉じる", + "403.title": "アクセス拒否", + "403.message": "アクセスが拒否されました.ログインしますか?", + "404.title": "見つかりません", + "404.message": "アクセスしようとしていたページは存在しません.ホームへ", + "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": "ゲスト" +} diff --git a/public/language/ja/language.json b/public/language/ja/language.json new file mode 100644 index 0000000000..19a14e500a --- /dev/null +++ b/public/language/ja/language.json @@ -0,0 +1,5 @@ +{ + "name": "日本語", + "code": "ja", + "dir": "ltr" +} diff --git a/public/language/ja/login.json b/public/language/ja/login.json new file mode 100644 index 0000000000..a16f10ddf2 --- /dev/null +++ b/public/language/ja/login.json @@ -0,0 +1,8 @@ +{ + "username": "ユーザー名 / メール", + "remember_me": "ログイン情報を記憶", + "forgot_password": "パスワードがわからない?", + "alternative_logins": "ほかのログイン方法", + "failed_login_attempt": "ログインに失敗しました.ユーザー名やパスワードをご確認ください。", + "login_successful": "ログインしました!" +} diff --git a/public/language/ja/modules.json b/public/language/ja/modules.json new file mode 100644 index 0000000000..7a01eeca56 --- /dev/null +++ b/public/language/ja/modules.json @@ -0,0 +1,7 @@ +{ + "chat.chatting_with": "とチャット", + "chat.placeholder": "メッセージを入力する", + "chat.send": "送信", + "chat.no_active": "チャットはありません。", + "chat.user_typing": "%1 は入力中 ..." +} diff --git a/public/language/ja/notifications.json b/public/language/ja/notifications.json new file mode 100644 index 0000000000..de907f0fcc --- /dev/null +++ b/public/language/ja/notifications.json @@ -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": "%1は新しいポストを投稿しました。", + "new_message_from": "%1からの新しいメッセージ", + "upvoted_your_post": "%1はあなたのポストを評価しました。", + "favourited_your_post": "%1はあなたのポストをお気に入りにしました。", + "user_flagged_post": "%1 ポストを報告しました。", + "user_posted_to": "%1%2 への返事を作成しました。" +} diff --git a/public/language/ja/pages.json b/public/language/ja/pages.json new file mode 100644 index 0000000000..8387697fe1 --- /dev/null +++ b/public/language/ja/pages.json @@ -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": "ユーザー設定" +} diff --git a/public/language/ja/recent.json b/public/language/ja/recent.json new file mode 100644 index 0000000000..88125f8432 --- /dev/null +++ b/public/language/ja/recent.json @@ -0,0 +1,7 @@ +{ + "title": "最近の更新", + "day": "最近 1 日", + "week": "最近 1 週", + "month": "最近 1 ヶ月", + "no_recent_topics": "最近のスレッドはありません。" +} diff --git a/public/language/ja/register.json b/public/language/ja/register.json new file mode 100644 index 0000000000..b1ce4c0794 --- /dev/null +++ b/public/language/ja/register.json @@ -0,0 +1,18 @@ +{ + "register": "登録", + "help.email": "デフォルト設定ではメールアドレスは公開されません。", + "help.username_restrictions": "%1 から %2 文字までのユニークなユーザー名.Twitter の @username 方式でメンションすることができます。", + "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": "利用規約に同意する" +} diff --git a/public/language/ja/reset_password.json b/public/language/ja/reset_password.json new file mode 100644 index 0000000000..4c2d9e6858 --- /dev/null +++ b/public/language/ja/reset_password.json @@ -0,0 +1,14 @@ +{ + "reset_password": "パスワードをリセット", + "update_password": "パスワードを更新", + "password_changed.title": "パスワードを更新しました", + "password_changed.message": "

パスワードをリセットできました.こちらでログインしてください。", + "wrong_reset_code.title": "リセットコードは正しくありません", + "wrong_reset_code.message": "リセットコードは正しくありません.もう一度入力するか、新しいリセットコードを請求することができます。", + "new_password": "新しいパスワード", + "repeat_password": "パスワード再入力", + "enter_email": "メールアドレスを入力してください.パスワードリセットの指示をメールでご送付します。", + "enter_email_address": "メールアドレスを入力してください", + "password_reset_sent": "パスワードリセットのメールを送信しました", + "invalid_email": "このメールアドレスは存在しません" +} diff --git a/public/language/ja/success.json b/public/language/ja/success.json new file mode 100644 index 0000000000..b8ea2c4056 --- /dev/null +++ b/public/language/ja/success.json @@ -0,0 +1,6 @@ +{ + "success": "成功", + "topic-post": "成功に投稿しました。", + "authentication-successful": "認証が成功しました", + "settings-saved": "設定を保存しました。" +} diff --git a/public/language/ja/topic.json b/public/language/ja/topic.json new file mode 100644 index 0000000000..d446179ca8 --- /dev/null +++ b/public/language/ja/topic.json @@ -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)" +} diff --git a/public/language/ja/unread.json b/public/language/ja/unread.json new file mode 100644 index 0000000000..a6027a54b3 --- /dev/null +++ b/public/language/ja/unread.json @@ -0,0 +1,9 @@ +{ + "title": "未読", + "no_unread_topics": "未読のスレッドがあります。", + "load_more": "もっと見る", + "mark_as_read": "既読にする", + "selected": "選択済み", + "all": "すべて", + "topics_marked_as_read.success": "すべてのスレッドを既読にしました。" +} diff --git a/public/language/ja/user.json b/public/language/ja/user.json new file mode 100644 index 0000000000..25ce70a83e --- /dev/null +++ b/public/language/ja/user.json @@ -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": "このユーザー名はすでに使用されています。いまのユーザー名は %1 です。", + "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": "通知が来たとき音を流す" +} diff --git a/public/language/ja/users.json b/public/language/ja/users.json new file mode 100644 index 0000000000..fa9b441a9a --- /dev/null +++ b/public/language/ja/users.json @@ -0,0 +1,8 @@ +{ + "latest_users": "最新ユーザー", + "top_posters": "最も投稿したユーザー", + "most_reputation": "最も評価されたユーザー", + "search": "検索", + "enter_username": "検索するユーザー名を入力してください", + "load_more": "もっと表示" +} diff --git a/public/language/vi/category.json b/public/language/vi/category.json new file mode 100644 index 0000000000..972df0d4f3 --- /dev/null +++ b/public/language/vi/category.json @@ -0,0 +1,7 @@ +{ + "new_topic_button": "Chủ đề mới", + "no_topics": "Không có bài viết trong danh mục.
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" +} \ No newline at end of file diff --git a/public/language/vi/error.json b/public/language/vi/error.json new file mode 100644 index 0000000000..3612b00e3d --- /dev/null +++ b/public/language/vi/error.json @@ -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!" +} \ No newline at end of file diff --git a/public/language/vi/global.json b/public/language/vi/global.json new file mode 100644 index 0000000000..433101d4cd --- /dev/null +++ b/public/language/vi/global.json @@ -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" +} \ No newline at end of file diff --git a/public/language/vi/language.json b/public/language/vi/language.json new file mode 100644 index 0000000000..f0787f748a --- /dev/null +++ b/public/language/vi/language.json @@ -0,0 +1,5 @@ +{ + "name": "Tiếng Việt", + "code": "vi", + "dir": "ltr" +} \ No newline at end of file diff --git a/public/language/vi/login.json b/public/language/vi/login.json new file mode 100644 index 0000000000..713509be4e --- /dev/null +++ b/public/language/vi/login.json @@ -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!" +} \ No newline at end of file diff --git a/public/language/vi/modules.json b/public/language/vi/modules.json new file mode 100644 index 0000000000..0f80e4414d --- /dev/null +++ b/public/language/vi/modules.json @@ -0,0 +1,7 @@ +{ + "chat.chatting_with": "Chat với ", + "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õ" +} \ No newline at end of file diff --git a/public/language/vi/notifications.json b/public/language/vi/notifications.json new file mode 100644 index 0000000000..5a83eafd01 --- /dev/null +++ b/public/language/vi/notifications.json @@ -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": "%1 đã viết bài mới", + "new_message_from": "Tin nhắn mới từ %1", + "upvoted_your_post": "%1 đã hủy vote cho bài viết của bạn", + "favourited_your_post": "%1 thích bài viết của bạn", + "user_flagged_post": "%1 đã flag một bài viết", + "user_posted_to": "%1 đã trả lời %2" +} \ No newline at end of file diff --git a/public/language/vi/pages.json b/public/language/vi/pages.json new file mode 100644 index 0000000000..36cb72eb47 --- /dev/null +++ b/public/language/vi/pages.json @@ -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" +} \ No newline at end of file diff --git a/public/language/vi/recent.json b/public/language/vi/recent.json new file mode 100644 index 0000000000..571084e721 --- /dev/null +++ b/public/language/vi/recent.json @@ -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" +} \ No newline at end of file diff --git a/public/language/vi/register.json b/public/language/vi/register.json new file mode 100644 index 0000000000..848cfdafa5 --- /dev/null +++ b/public/language/vi/register.json @@ -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 @tên truy cập.", + "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" +} \ No newline at end of file diff --git a/public/language/vi/reset_password.json b/public/language/vi/reset_password.json new file mode 100644 index 0000000000..edc196ba6c --- /dev/null +++ b/public/language/vi/reset_password.json @@ -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": "

Mật khẩu đã được thiết lập lại thành công, xin hãy đăng nhập lại.", + "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 yêu cầu một mã thiết lập lại khác.", + "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 địa chỉ email 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!" +} \ No newline at end of file diff --git a/public/language/vi/success.json b/public/language/vi/success.json new file mode 100644 index 0000000000..f88527f3ad --- /dev/null +++ b/public/language/vi/success.json @@ -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" +} \ No newline at end of file diff --git a/public/language/vi/topic.json b/public/language/vi/topic.json new file mode 100644 index 0000000000..3cae01a414 --- /dev/null +++ b/public/language/vi/topic.json @@ -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" +} \ No newline at end of file diff --git a/public/language/vi/unread.json b/public/language/vi/unread.json new file mode 100644 index 0000000000..634efe34d4 --- /dev/null +++ b/public/language/vi/unread.json @@ -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" +} \ No newline at end of file diff --git a/public/language/vi/user.json b/public/language/vi/user.json new file mode 100644 index 0000000000..fa9fc38c60 --- /dev/null +++ b/public/language/vi/user.json @@ -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à %1", + "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" +} \ No newline at end of file diff --git a/public/language/vi/users.json b/public/language/vi/users.json new file mode 100644 index 0000000000..e09f3c94ea --- /dev/null +++ b/public/language/vi/users.json @@ -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" +} \ No newline at end of file diff --git a/public/src/forum/admin/plugins.js b/public/src/forum/admin/plugins.js index 68b03c4b50..6246c48851 100644 --- a/public/src/forum/admin/plugins.js +++ b/public/src/forum/admin/plugins.js @@ -48,7 +48,7 @@ define(function() { btn.html(' 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); diff --git a/public/src/forum/admin/settings.js b/public/src/forum/admin/settings.js index cddd48428b..6bc5d782d0 100644 --- a/public/src/forum/admin/settings.js +++ b/public/src/forum/admin/settings.js @@ -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); }; diff --git a/public/src/forum/category.js b/public/src/forum/category.js index af69859270..e71dfc185f 100644 --- a/public/src/forum/category.js +++ b/public/src/forum/category.js @@ -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), diff --git a/public/src/forum/topic.js b/public/src/forum/topic.js index 3162f87f61..50954debfa 100644 --- a/public/src/forum/topic.js +++ b/public/src/forum/topic.js @@ -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); }); diff --git a/public/src/modules/composer.js b/public/src/modules/composer.js index 4972eaf3c3..6e57037a47 100644 --- a/public/src/modules/composer.js +++ b/public/src/modules/composer.js @@ -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, diff --git a/public/src/modules/topicSelect.js b/public/src/modules/topicSelect.js index 4fbbc38f12..a5b131a7a2 100644 --- a/public/src/modules/topicSelect.js +++ b/public/src/modules/topicSelect.js @@ -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; }); \ No newline at end of file diff --git a/src/categories.js b/src/categories.js index 11c86e81fd..713ce64220 100644 --- a/src/categories.js +++ b/src/categories.js @@ -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); }); }; diff --git a/src/categories/activeusers.js b/src/categories/activeusers.js index 9b8e558aae..57873944b8 100644 --- a/src/categories/activeusers.js +++ b/src/categories/activeusers.js @@ -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); - } - } - }); - } }); }; }; diff --git a/src/categories/recentreplies.js b/src/categories/recentreplies.js index 33ee629811..925e83e42e 100644 --- a/src/categories/recentreplies.js +++ b/src/categories/recentreplies.js @@ -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); } }); - } + }); }); }; }; diff --git a/src/controllers/admin/uploads.js b/src/controllers/admin/uploads.js index e7c79d6e7e..e0543c9add 100644 --- a/src/controllers/admin/uploads.js +++ b/src/controllers/admin/uploads.js @@ -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; diff --git a/src/plugins.js b/src/plugins.js index d660d90eae..832c43fef1 100644 --- a/src/plugins.js +++ b/src/plugins.js @@ -484,27 +484,60 @@ var fs = require('fs'), return callback(null, []); } - async.map(plugins, function(plugin, next) { + var pluginMap = {}; + for(var i=0; i 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) { diff --git a/src/socket.io/meta.js b/src/socket.io/meta.js index 8c40d0ef52..35039a9dd4 100644 --- a/src/socket.io/meta.js +++ b/src/socket.io/meta.js @@ -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 } diff --git a/src/socket.io/modules.js b/src/socket.io/modules.js index 9d8d233c64..bc243855ab 100644 --- a/src/socket.io/modules.js +++ b/src/socket.io/modules.js @@ -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; } diff --git a/src/socket.io/posts.js b/src/socket.io/posts.js index dde786af7e..7398ea28cd 100644 --- a/src/socket.io/posts.js +++ b/src/socket.io/posts.js @@ -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) { diff --git a/src/socket.io/topics.js b/src/socket.io/topics.js index 5b8da28dcf..6ca38f9ce9 100644 --- a/src/socket.io/topics.js +++ b/src/socket.io/topics.js @@ -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) { diff --git a/src/threadTools.js b/src/threadTools.js index 55bac851b4..0ffbea46d4 100644 --- a/src/threadTools.js +++ b/src/threadTools.js @@ -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)); diff --git a/src/topics.js b/src/topics.js index 7ff3d6e68f..43dd9dadf2 100644 --- a/src/topics.js +++ b/src/topics.js @@ -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); } diff --git a/src/topics/create.js b/src/topics/create.js index f597b53d7d..3072dba2ae 100644 --- a/src/topics/create.js +++ b/src/topics/create.js @@ -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); diff --git a/src/topics/follow.js b/src/topics/follow.js new file mode 100644 index 0000000000..7e86d9a019 --- /dev/null +++ b/src/topics/follow.js @@ -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); + } + }); + }; + +}; \ No newline at end of file diff --git a/src/topics/posts.js b/src/topics/posts.js index 6d59daf763..53ff57f5a4 100644 --- a/src/topics/posts.js +++ b/src/topics/posts.js @@ -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); }; diff --git a/src/user.js b/src/user.js index 202b511df8..10812d551e 100644 --- a/src/user.js +++ b/src/user.js @@ -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); }); }; diff --git a/src/user/delete.js b/src/user/delete.js index c7e4ae1169..33f0f8d10e 100644 --- a/src/user/delete.js +++ b/src/user/delete.js @@ -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) {