",
- "register": "Registeren",
+ "register": "Registreren",
"login": "Login",
"please-log-in": "Aanmelden",
"logout": "Uitloggen",
diff --git a/public/language/nl/modules.json b/public/language/nl/modules.json
index a252352d8b..89f725497f 100644
--- a/public/language/nl/modules.json
+++ b/public/language/nl/modules.json
@@ -1,8 +1,9 @@
{
"chat.room-id": "Room %1",
"chat.chatting-with": "Chat met",
- "chat.placeholder": "Type chat message here, drag & drop images, press enter to send",
- "chat.placeholder.mobile": "Type chat message here",
+ "chat.placeholder": "Type chat message here, drag & drop images",
+ "chat.placeholder.mobile": "Type chat message",
+ "chat.placeholder.message-room": "Message #%1",
"chat.scroll-up-alert": "Go to most recent message",
"chat.usernames-and-x-others": "%1 & %2 others",
"chat.chat-with-usernames": "Chat with %1",
diff --git a/public/language/pl/modules.json b/public/language/pl/modules.json
index 06e1de129d..e118ef0ee3 100644
--- a/public/language/pl/modules.json
+++ b/public/language/pl/modules.json
@@ -1,8 +1,9 @@
{
"chat.room-id": "Pokój %1",
"chat.chatting-with": "Czatuj z",
- "chat.placeholder": "Wpisz tutaj wiadomość, przeciągnij i opuść obrazki, wciśnij enter aby wysłać",
- "chat.placeholder.mobile": "Tutaj napisz wiadomość",
+ "chat.placeholder": "Type chat message here, drag & drop images",
+ "chat.placeholder.mobile": "Type chat message",
+ "chat.placeholder.message-room": "Message #%1",
"chat.scroll-up-alert": "Go to most recent message",
"chat.usernames-and-x-others": "%1 i %2 innych",
"chat.chat-with-usernames": "Czatuj z %1",
diff --git a/public/language/pt-BR/modules.json b/public/language/pt-BR/modules.json
index 3d803930c0..26eb190f69 100644
--- a/public/language/pt-BR/modules.json
+++ b/public/language/pt-BR/modules.json
@@ -1,8 +1,9 @@
{
"chat.room-id": "Room %1",
"chat.chatting-with": "Conversar com",
- "chat.placeholder": "Type chat message here, drag & drop images, press enter to send",
- "chat.placeholder.mobile": "Type chat message here",
+ "chat.placeholder": "Type chat message here, drag & drop images",
+ "chat.placeholder.mobile": "Type chat message",
+ "chat.placeholder.message-room": "Message #%1",
"chat.scroll-up-alert": "Go to most recent message",
"chat.usernames-and-x-others": "%1 & %2 others",
"chat.chat-with-usernames": "Chat with %1",
diff --git a/public/language/pt-PT/modules.json b/public/language/pt-PT/modules.json
index 72f2a10f89..367ef43725 100644
--- a/public/language/pt-PT/modules.json
+++ b/public/language/pt-PT/modules.json
@@ -1,8 +1,9 @@
{
"chat.room-id": "Room %1",
"chat.chatting-with": "Conversar com",
- "chat.placeholder": "Type chat message here, drag & drop images, press enter to send",
- "chat.placeholder.mobile": "Type chat message here",
+ "chat.placeholder": "Type chat message here, drag & drop images",
+ "chat.placeholder.mobile": "Type chat message",
+ "chat.placeholder.message-room": "Message #%1",
"chat.scroll-up-alert": "Go to most recent message",
"chat.usernames-and-x-others": "%1 & %2 others",
"chat.chat-with-usernames": "Chat with %1",
diff --git a/public/language/ro/modules.json b/public/language/ro/modules.json
index 39a9da3cd2..c637d7e8bb 100644
--- a/public/language/ro/modules.json
+++ b/public/language/ro/modules.json
@@ -1,8 +1,9 @@
{
"chat.room-id": "Room %1",
"chat.chatting-with": "Chat with",
- "chat.placeholder": "Type chat message here, drag & drop images, press enter to send",
- "chat.placeholder.mobile": "Type chat message here",
+ "chat.placeholder": "Type chat message here, drag & drop images",
+ "chat.placeholder.mobile": "Type chat message",
+ "chat.placeholder.message-room": "Message #%1",
"chat.scroll-up-alert": "Go to most recent message",
"chat.usernames-and-x-others": "%1 & %2 others",
"chat.chat-with-usernames": "Chat with %1",
diff --git a/public/language/ru/modules.json b/public/language/ru/modules.json
index 0519451b3c..fe0dcb2f7e 100644
--- a/public/language/ru/modules.json
+++ b/public/language/ru/modules.json
@@ -1,8 +1,9 @@
{
"chat.room-id": "Room %1",
"chat.chatting-with": "Чат с",
- "chat.placeholder": "Введите сообщение, перетащите изображения, нажмите enter для отправки",
- "chat.placeholder.mobile": "Введите сообщение здесь",
+ "chat.placeholder": "Type chat message here, drag & drop images",
+ "chat.placeholder.mobile": "Type chat message",
+ "chat.placeholder.message-room": "Message #%1",
"chat.scroll-up-alert": "Go to most recent message",
"chat.usernames-and-x-others": "%1 пользователей и %2 других",
"chat.chat-with-usernames": "Чат с %1",
diff --git a/public/language/rw/modules.json b/public/language/rw/modules.json
index 5f531d7b66..6181e328f2 100644
--- a/public/language/rw/modules.json
+++ b/public/language/rw/modules.json
@@ -1,8 +1,9 @@
{
"chat.room-id": "Room %1",
"chat.chatting-with": "Chat with",
- "chat.placeholder": "Type chat message here, drag & drop images, press enter to send",
- "chat.placeholder.mobile": "Type chat message here",
+ "chat.placeholder": "Type chat message here, drag & drop images",
+ "chat.placeholder.mobile": "Type chat message",
+ "chat.placeholder.message-room": "Message #%1",
"chat.scroll-up-alert": "Go to most recent message",
"chat.usernames-and-x-others": "%1 & %2 others",
"chat.chat-with-usernames": "Chat with %1",
diff --git a/public/language/sc/modules.json b/public/language/sc/modules.json
index 00e8a42142..b01c92446b 100644
--- a/public/language/sc/modules.json
+++ b/public/language/sc/modules.json
@@ -1,8 +1,9 @@
{
"chat.room-id": "Room %1",
"chat.chatting-with": "Chat with",
- "chat.placeholder": "Type chat message here, drag & drop images, press enter to send",
- "chat.placeholder.mobile": "Type chat message here",
+ "chat.placeholder": "Type chat message here, drag & drop images",
+ "chat.placeholder.mobile": "Type chat message",
+ "chat.placeholder.message-room": "Message #%1",
"chat.scroll-up-alert": "Go to most recent message",
"chat.usernames-and-x-others": "%1 & %2 others",
"chat.chat-with-usernames": "Chat with %1",
diff --git a/public/language/sk/modules.json b/public/language/sk/modules.json
index 76abdaac69..1070b9f944 100644
--- a/public/language/sk/modules.json
+++ b/public/language/sk/modules.json
@@ -1,8 +1,9 @@
{
"chat.room-id": "Room %1",
"chat.chatting-with": "Konverzácia s",
- "chat.placeholder": "Type chat message here, drag & drop images, press enter to send",
- "chat.placeholder.mobile": "Type chat message here",
+ "chat.placeholder": "Type chat message here, drag & drop images",
+ "chat.placeholder.mobile": "Type chat message",
+ "chat.placeholder.message-room": "Message #%1",
"chat.scroll-up-alert": "Go to most recent message",
"chat.usernames-and-x-others": "%1 & %2 others",
"chat.chat-with-usernames": "Chat with %1",
diff --git a/public/language/sl/modules.json b/public/language/sl/modules.json
index a95028d95f..a15faa5681 100644
--- a/public/language/sl/modules.json
+++ b/public/language/sl/modules.json
@@ -1,8 +1,9 @@
{
"chat.room-id": "Room %1",
"chat.chatting-with": "Klepetajte z",
- "chat.placeholder": "Type chat message here, drag & drop images, press enter to send",
- "chat.placeholder.mobile": "Type chat message here",
+ "chat.placeholder": "Type chat message here, drag & drop images",
+ "chat.placeholder.mobile": "Type chat message",
+ "chat.placeholder.message-room": "Message #%1",
"chat.scroll-up-alert": "Go to most recent message",
"chat.usernames-and-x-others": "%1 & %2 others",
"chat.chat-with-usernames": "Chat with %1",
diff --git a/public/language/sq-AL/modules.json b/public/language/sq-AL/modules.json
index 8d6f61e825..48554b92cc 100644
--- a/public/language/sq-AL/modules.json
+++ b/public/language/sq-AL/modules.json
@@ -1,8 +1,9 @@
{
"chat.room-id": "Room %1",
"chat.chatting-with": "Bisedo me",
- "chat.placeholder": "Shkruani mesazhin e bisedës këtu, tërhiqni dhe lëshoni imazhet, shtypni enter për t'i dërguar",
- "chat.placeholder.mobile": "Type chat message here",
+ "chat.placeholder": "Type chat message here, drag & drop images",
+ "chat.placeholder.mobile": "Type chat message",
+ "chat.placeholder.message-room": "Message #%1",
"chat.scroll-up-alert": "Go to most recent message",
"chat.usernames-and-x-others": "%1 & %2 others",
"chat.chat-with-usernames": "Chat with %1",
diff --git a/public/language/sr/modules.json b/public/language/sr/modules.json
index 717904742c..c2bae5b14e 100644
--- a/public/language/sr/modules.json
+++ b/public/language/sr/modules.json
@@ -1,8 +1,9 @@
{
"chat.room-id": "Соба %1",
"chat.chatting-with": "Ћаскај са",
- "chat.placeholder": "Куцајте поруку ћаскања овде, превуците и отпустите слике, притисните enter за слање",
- "chat.placeholder.mobile": "Куцајте поруку овде",
+ "chat.placeholder": "Type chat message here, drag & drop images",
+ "chat.placeholder.mobile": "Type chat message",
+ "chat.placeholder.message-room": "Message #%1",
"chat.scroll-up-alert": "Идите на најновију поруку",
"chat.usernames-and-x-others": "%1 & %2 осталих",
"chat.chat-with-usernames": "Ћаскај са %1",
diff --git a/public/language/sv/modules.json b/public/language/sv/modules.json
index 779b08ad3f..a4e16381e2 100644
--- a/public/language/sv/modules.json
+++ b/public/language/sv/modules.json
@@ -1,8 +1,9 @@
{
"chat.room-id": "Room %1",
"chat.chatting-with": "Chatta med",
- "chat.placeholder": "Type chat message here, drag & drop images, press enter to send",
- "chat.placeholder.mobile": "Type chat message here",
+ "chat.placeholder": "Type chat message here, drag & drop images",
+ "chat.placeholder.mobile": "Type chat message",
+ "chat.placeholder.message-room": "Message #%1",
"chat.scroll-up-alert": "Go to most recent message",
"chat.usernames-and-x-others": "%1 & %2 others",
"chat.chat-with-usernames": "Chat with %1",
diff --git a/public/language/th/modules.json b/public/language/th/modules.json
index 2a3ae08a34..4aff8669ce 100644
--- a/public/language/th/modules.json
+++ b/public/language/th/modules.json
@@ -1,8 +1,9 @@
{
"chat.room-id": "ห้อง %1",
"chat.chatting-with": "คุยกับ",
- "chat.placeholder": "พิมพ์ข้อความแชทที่นี้ ลากและปล่อยรูปภาพ กดปุ่ม enter เพื่อส่ง",
- "chat.placeholder.mobile": "พิมพ์ข้อความแชทที่นี่",
+ "chat.placeholder": "Type chat message here, drag & drop images",
+ "chat.placeholder.mobile": "Type chat message",
+ "chat.placeholder.message-room": "Message #%1",
"chat.scroll-up-alert": "ไปที่ข้อความล่าสุด",
"chat.usernames-and-x-others": "%1, %2 และผู้อื่น",
"chat.chat-with-usernames": "แชทกับ %1",
diff --git a/public/language/tr/modules.json b/public/language/tr/modules.json
index 61aa0841b7..53ef99d661 100644
--- a/public/language/tr/modules.json
+++ b/public/language/tr/modules.json
@@ -1,8 +1,9 @@
{
"chat.room-id": "Oda %1",
"chat.chatting-with": "Sohbet",
- "chat.placeholder": "Mesajı yazın veya resim sürükleyip bırakın",
- "chat.placeholder.mobile": "Sohbet içeriğini buraya giriniz",
+ "chat.placeholder": "Type chat message here, drag & drop images",
+ "chat.placeholder.mobile": "Type chat message",
+ "chat.placeholder.message-room": "Message #%1",
"chat.scroll-up-alert": "En son yazılan mesaja geri dönün",
"chat.usernames-and-x-others": "%1 & %2 başka kişi",
"chat.chat-with-usernames": "%1 ile sohbet edin",
diff --git a/public/language/uk/modules.json b/public/language/uk/modules.json
index 7c61d6bea9..4c280a1356 100644
--- a/public/language/uk/modules.json
+++ b/public/language/uk/modules.json
@@ -1,8 +1,9 @@
{
"chat.room-id": "Room %1",
"chat.chatting-with": "Чат з",
- "chat.placeholder": "Type chat message here, drag & drop images, press enter to send",
- "chat.placeholder.mobile": "Type chat message here",
+ "chat.placeholder": "Type chat message here, drag & drop images",
+ "chat.placeholder.mobile": "Type chat message",
+ "chat.placeholder.message-room": "Message #%1",
"chat.scroll-up-alert": "Go to most recent message",
"chat.usernames-and-x-others": "%1 & %2 others",
"chat.chat-with-usernames": "Chat with %1",
diff --git a/public/language/vi/admin/appearance/customise.json b/public/language/vi/admin/appearance/customise.json
index 873e939f86..566f693fb0 100644
--- a/public/language/vi/admin/appearance/customise.json
+++ b/public/language/vi/admin/appearance/customise.json
@@ -16,5 +16,5 @@
"custom-css.livereload.description": "Bật điều này để buộc tất cả các phiên trên mọi thiết bị trong tài khoản của bạn phải làm mới bất cứ khi nào bạn nhấp vào lưu",
"bsvariables": "_variables.scss",
"bsvariables.description": "Ghi đè các biến bootstrap ở đây. Bạn có thể sử dụng công cụ như bootstrap.build và dán đầu ra ở đây. Thay đổi buộc dựng lại và chạy lại.",
- "bsvariables.enable": "Enable _variables.scss"
+ "bsvariables.enable": "Bật _variables.scss"
}
\ No newline at end of file
diff --git a/public/language/vi/admin/appearance/themes.json b/public/language/vi/admin/appearance/themes.json
index c468abc085..bd3d096acd 100644
--- a/public/language/vi/admin/appearance/themes.json
+++ b/public/language/vi/admin/appearance/themes.json
@@ -3,7 +3,7 @@
"checking-for-installed": "Đang kiểm tra các giao diện đã cài đặt...",
"homepage": "Trang chủ",
"select-theme": "Chọn Giao Diện",
- "revert-theme": "Revert Theme",
+ "revert-theme": "Hoàn Nguyên Chủ Đề",
"current-theme": "Giao Diện Hiện Tại",
"no-themes": "Không tìm thấy giao diện đã cài đặt",
"revert-confirm": "Bạn có chắc muốn khôi phục giao diện NodeBB mặc định không?",
diff --git a/public/language/vi/admin/dashboard.json b/public/language/vi/admin/dashboard.json
index b9725d4da8..11e81265b2 100644
--- a/public/language/vi/admin/dashboard.json
+++ b/public/language/vi/admin/dashboard.json
@@ -44,8 +44,8 @@
"control-panel": "Điều khiển hệ thống",
"rebuild-and-restart": "Dựng lại & Chạy lại",
"restart": "Chạy lại",
- "restart-warning": "Xây dựng lại hoặc Khởi động lại NodeBB của bạn sẽ hủy tất cả các kết nối hiện có trong vài giây.",
- "restart-disabled": "Việc xây dựng lại và khởi động lại NodeBB của bạn đã bị vô hiệu hóa vì bạn dường như không chạy nó qua daemon thích hợp.",
+ "restart-warning": "Dựng lại hay Chạy lại NodeBB của bạn sẽ hủy tất cả các kết nối hiện có trong vài giây.",
+ "restart-disabled": "Dựng lại và Chạy lại NodeBB của bạn đã bị vô hiệu hóa vì bạn dường như không chạy nó qua daemon thích hợp.",
"maintenance-mode": "Chế Độ Bảo Trì",
"maintenance-mode-title": "Bấm vào đây để thiết lập chế độ bảo trì cho NodeBB",
"dark-mode": "Chế Độ Tối",
@@ -62,7 +62,7 @@
"registered": "Đã đăng ký",
"user-presence": "Người Dùng Có Mặt",
- "on-categories": "Trên Danh Sách Chuyên Mục",
+ "on-categories": "Trên danh sách danh mục",
"reading-posts": "Đọc bài viết",
"browsing-topics": "Duyệt qua chủ đề",
"recent": "Gần đây",
diff --git a/public/language/vi/admin/development/logger.json b/public/language/vi/admin/development/logger.json
index 20270606d0..bbe6db943a 100644
--- a/public/language/vi/admin/development/logger.json
+++ b/public/language/vi/admin/development/logger.json
@@ -1,5 +1,5 @@
{
- "logger": "Logger",
+ "logger": "Ghi Nhật Ký",
"logger-settings": "Cài Đặt Ghi Nhật Ký",
"description": "Bật cái này, bạn sẽ nhận nhật ký ở công cụ dòng lệnh của bạn. Nếu có đường dẫn cụ thể, nhật ký sẽ được lưu vào một tệp thay thế. Nhật ký HTTP có lợi để thu thập thống kê ai đó, khi nào và những gì họ làm diễn đàn. Ngoài ghi nhật ký yêu cầu HTTP, chúng ta có thể ghi nhật ký sự kiện socket.io. Ghi nhật ký Socket.io, kết hợp giám sát redis-cli, có thể hữu ích để tìm hiểu bên trong NodeBB.",
"explanation": "Chỉ cần chọn/bỏ chọn cài đặt ghi nhật ký để bật hoặc tắt ghi nhật ký một cách nhanh chóng. Không cần khởi động lại.",
diff --git a/public/language/vi/admin/extend/plugins.json b/public/language/vi/admin/extend/plugins.json
index 60edca071c..6716e75d18 100644
--- a/public/language/vi/admin/extend/plugins.json
+++ b/public/language/vi/admin/extend/plugins.json
@@ -40,9 +40,9 @@
"alert.upgraded": "Đã Nâng Cấp Plugin",
"alert.installed": "Đã Cài Đặt Plugin",
"alert.uninstalled": "Đã Gỡ Bỏ Plugin",
- "alert.activate-success": "Vui lòng xây dựng lại và khởi động lại NodeBB của bạn để kích hoạt hoàn toàn plugin này",
+ "alert.activate-success": "Hãy dựng lại và chạy lại NodeBB của bạn để kích hoạt plugin này",
"alert.deactivate-success": "Đã hủy kích hoạt plugin thành công",
- "alert.upgrade-success": "Vui lòng xây dựng lại và khởi động lại NodeBB của bạn để nâng cấp đầy đủ plugin này.",
+ "alert.upgrade-success": "Hãy dựng lại và chạy lại NodeBB của bạn để nâng cấp plugin này.",
"alert.install-success": "Đã cài đặt thành công plugin, vui lòng kích hoạt plugin.",
"alert.uninstall-success": "Đã hủy kích hoạt và gỡ cài đặt plugin thành công.",
"alert.suggest-error": "
NodeBB không thể tiếp cận trình quản lý gói, hãy tiến hành cài đặt phiên bản mới nhất?
Máy chủ trả về (%1): %2
",
@@ -50,7 +50,7 @@
"alert.incompatible": "
Phiên bản NodeBB (v%1) của bạn chỉ được xóa để nâng cấp lên v%2 của plugin này. Vui lòng cập nhật NodeBB của bạn nếu muốn cài đặt phiên bản mới hơn của plugin này.
",
"alert.possibly-incompatible": "
Không Có Thông Tin Tương Thích
Plugin này không đưa ra một phiên bản cụ thể để cài đặt với phiên bản NodeBB của bạn. Không đảm bảo khả năng tương thích hoàn toàn và có thể khiến NodeBB của bạn không hoạt động bình thường.
Trường hợp NodeBB không thể hoạt động đúng:
$ ./nodebb reset plugin=\"%1\"
Tiếp tục cài đặt phiên bản mới nhất của plugin này?
",
"alert.reorder": "Các Plugin Đã Được Sắp Xếp Lại",
- "alert.reorder-success": "Vui lòng xây dựng lại và khởi động lại NodeBB của bạn để hoàn tất quá trình.",
+ "alert.reorder-success": "Hãy dựng lại và chạy lại NodeBB của bạn để hoàn tất quá trình.",
"license.title": "Thông Tin Cấp Phép Plugin",
"license.intro": "Plugin %1 được cấp phép theo %2. Vui lòng đọc và hiểu các điều khoản cấp phép trước khi kích hoạt plugin này.",
diff --git a/public/language/vi/admin/extend/rewards.json b/public/language/vi/admin/extend/rewards.json
index bbb0c9d22d..ba946f9af6 100644
--- a/public/language/vi/admin/extend/rewards.json
+++ b/public/language/vi/admin/extend/rewards.json
@@ -1,12 +1,12 @@
{
"rewards": "Phần thưởng",
- "add-reward": "Add reward",
+ "add-reward": "Thêm phần thưởng",
"condition-if-users": "Nếu Người Dùng",
"condition-is": "Là:",
"condition-then": "Sau đó:",
"max-claims": "Số lần nhận thưởng có thể nhận được",
"zero-infinite": "Nhập 0 cho vô hạn",
- "select-reward": "Select reward",
+ "select-reward": "Chọn phần thưởng",
"delete": "Xóa",
"enable": "Bật",
"disable": "Tắt",
diff --git a/public/language/vi/admin/extend/widgets.json b/public/language/vi/admin/extend/widgets.json
index 271ee1eede..b62f5eec98 100644
--- a/public/language/vi/admin/extend/widgets.json
+++ b/public/language/vi/admin/extend/widgets.json
@@ -1,5 +1,5 @@
{
- "widgets": "Widgets",
+ "widgets": "Tiện ích",
"available": "Tiện ích có sẵn",
"explanation": "Chọn một tiện ích từ menu thả xuống, sau đó kéo và thả nó vào khu vực tiện ích của mẫu ở bên trái.",
"none-installed": "Không tìm thấy tiện ích nào! Kích hoạt plugin tiện ích cần thiết trong bảng điều khiểnplugins .",
diff --git a/public/language/vi/admin/manage/digest.json b/public/language/vi/admin/manage/digest.json
index 19df6c552c..0a3af43dfb 100644
--- a/public/language/vi/admin/manage/digest.json
+++ b/public/language/vi/admin/manage/digest.json
@@ -1,10 +1,10 @@
{
"lead": "Một danh sách các số liệu thống kê và thời gian phân phối được hiển thị dưới đây.",
- "disclaimer": "Xin lưu ý rằng việc gửi email không được đảm bảo, do bản chất của công nghệ email. Nhiều yếu tố quyết định đến việc liệu một email được gửi đến máy chủ người nhận cuối cùng có được gửi đến hộp thư đến của người dùng hay không, bao gồm danh tiếng của máy chủ, địa chỉ IP nằm trong danh sách đen và liệu DKIM/SPF/DMARC được cấu hình.",
+ "disclaimer": "Lưu ý việc gửi email là không đảm bảo, do bản chất của công nghệ email. Nhiều yếu tố ảnh hưởng đến một email được gửi đến máy chủ người nhận cuối cùng có được gửi đến hộp thư của người dùng hay không, bao gồm danh tiếng máy chủ, địa chỉ IP nằm trong danh sách đen và liệu DKIM/SPF/DMARC được cấu hình.",
"disclaimer-continued": "Gửi thành công nghĩa là tin nhắn được NodeBB gửi thành công và máy chủ người nhận nhận được. Nó không có nghĩa là email đã đến hộp thư đến. Để có kết quả tốt nhất, chúng tôi khuyên bạn nên sử dụng dịch vụ gửi email của bên thứ ba, chẳng hạn như SendGrid.",
"user": "Người dùng",
- "subscription": "Loại đăng ký",
+ "subscription": "Loại Đăng Ký",
"last-delivery": "Gửi thành công lần cuối",
"default": "Mặc định hệ thống",
"default-help": "Mặc định hệ thống nghĩa là người dùng không ghi đè lên toàn bộ cài đặt thông báo diễn đàn, hiện là: "%1"",
diff --git a/public/language/vi/admin/manage/uploads.json b/public/language/vi/admin/manage/uploads.json
index 2df6f9ba9d..32f4d447e3 100644
--- a/public/language/vi/admin/manage/uploads.json
+++ b/public/language/vi/admin/manage/uploads.json
@@ -2,11 +2,11 @@
"manage-uploads": "Quản Lý Tải Lên",
"upload-file": "Tải Lên Tệp",
"filename": "Tên Tệp",
- "usage": "Đăng sử dụng",
+ "usage": "Dùng Bài Đăng",
"orphaned": "Đơn độc",
- "size/filecount": "Kích cỡ/ Số lượng tệp",
+ "size/filecount": "Kích cỡ/ Số tệp",
"confirm-delete": "Bạn có chắc muốn xóa tệp này không?",
"filecount": "%1 tệp",
- "new-folder": "Thư mục mới",
+ "new-folder": "Thư Mục Mới",
"name-new-folder": "Nhập tên cho thư mục mới"
}
\ No newline at end of file
diff --git a/public/language/vi/admin/settings/api.json b/public/language/vi/admin/settings/api.json
index 97db24b9e4..fb878a94b3 100644
--- a/public/language/vi/admin/settings/api.json
+++ b/public/language/vi/admin/settings/api.json
@@ -13,17 +13,17 @@
"token": "Token",
"uid-help-text": "Ghi rõ ID người dùng liên kết với mã truy cập. Nếu ID người dùng là 0, nó sẽ là môt mã truy cập cao cấp, có thể giả định danh tính của những người dùng khác dựa trên tham số _uid",
"description": "Mô tả",
- "last-seen": "Last seen",
- "created": "Created",
- "create-token": "Create Token",
+ "last-seen": "Nhìn thấy lần cuối",
+ "created": "Đã tạo",
+ "create-token": "Tạo Token",
"update-token": "Cập Nhật Token",
- "master-token": "Master token",
- "last-seen-never": "Khóa này chưa bao giờ được dùng.",
+ "master-token": "Token Chính",
+ "last-seen-never": "Khóa này chưa bao giờ dùng.",
"no-description": "Không có mô tả cụ thể.",
"actions": "Hành Động",
"edit": "Sửa",
"roll": "Cuộn",
- "delete-confirm": "Bạn có chắc chắn muốn xóa mã thông báo này không? Nó sẽ không thể phục hồi được.",
- "roll-confirm": "Bạn có chắc chắn muốn tạo lại mã thông báo này không? Mã thông báo cũ sẽ bị thu hồi ngay lập tức và không thể phục hồi được."
+ "delete-confirm": "Bạn có chắc muốn xóa token này không? Nó không thể phục hồi.",
+ "roll-confirm": "Bạn có chắc muốn tạo lại token này không? Token cũ sẽ bị thu hồi ngay và không thể phục hồi được."
}
\ No newline at end of file
diff --git a/public/language/vi/admin/settings/general.json b/public/language/vi/admin/settings/general.json
index 370b10d03d..6ed6f7e2e2 100644
--- a/public/language/vi/admin/settings/general.json
+++ b/public/language/vi/admin/settings/general.json
@@ -24,7 +24,7 @@
"logo.upload": "Tải lên",
"logo.url": "Liên kết URL Logo",
"logo.url-placeholder": "URL biểu trưng trang web",
- "logo.url-help": "Khi nhấp vào logo, hãy đưa người dùng đến địa chỉ này. Nếu để trống, người dùng sẽ được chuyển đến chỉ mục diễn đàn. Lưu ý: Đây không phải là URL bên ngoài được sử dụng trong email, v.v. Nó được đặt bởi thuộc tính url trong config.json",
+ "logo.url-help": "Khi nhấp vào logo, đưa người dùng đến địa chỉ này. Nếu để trống, người dùng sẽ được chuyển đến chỉ mục diễn đàn. Lưu ý: Đây không phải là URL bên ngoài được sử dụng trong email, v.v. Nó được đặt bởi thuộc tính url trong config.json",
"logo.alt-text": "Văn Bản Thay Thế",
"log.alt-text-placeholder": "Văn bản thay thế cho khả năng tiếp cận",
"favicon": "Biểu tượng ưa thích",
@@ -32,21 +32,21 @@
"pwa": "Ứng Dụng Web Tiến Bộ",
"touch-icon": "Biểu Tượng Cảm Ứng",
"touch-icon.upload": "Tải lên",
- "touch-icon.help": "Kích thước và định dạng được đề xuất: 512x512, chỉ định dạng PNG. Nếu không có biểu tượng cảm ứng nào, NodeBB sẽ quay trở lại sử dụng favicon.",
- "maskable-icon": "Biểu tượng có thể che được (Màn Trang Chủ)",
- "maskable-icon.help": "Kích thước và định dạng nên là: 512x512, chỉ định dạng PNG. Nếu không có biểu tượng có thể che được nào được chỉ định, NodeBB sẽ trở lại Biểu tượng cảm ứng.",
+ "touch-icon.help": "Kích cỡ và định dạng được đề xuất: 512x512, chỉ định dạng PNG. Nếu không có biểu tượng cảm ứng nào, NodeBB sẽ quay trở lại sử dụng favicon.",
+ "maskable-icon": "Biểu tượng có thể che được (Màn Hình Trang Chủ)",
+ "maskable-icon.help": "Kích thước và định dạng nên là: 512x512, chỉ định dạng PNG. Nếu không có biểu tượng có thể che được nào được chỉ định, NodeBB sẽ trở lại Biểu Tượng Chạm.",
"outgoing-links": "Liên Kết Đi",
"outgoing-links.warning-page": "Sử Dụng Trang Cảnh Báo Liên Kết Đi",
"search": "Tìm kiếm",
- "search-default-in": "Tìm kiếm trong",
- "search-default-in-quick": "Tìm kiếm nhanh trong",
- "search-default-sort-by": "Sắp xếp theo",
+ "search-default-in": "Tìm Trong",
+ "search-default-in-quick": "Tìm Nhanh Trong",
+ "search-default-sort-by": "Xếp theo",
"outgoing-links.whitelist": "Các tên miền trong danh sách trắng sẽ bỏ qua trang cảnh báo",
"site-colors": "Dữ Liệu Mô Tả Màu Trang",
"theme-color": "Màu Giao Diện",
"background-color": "Màu Nền",
"background-color-help": "Màu được sử dụng cho nền màn hình khởi động khi trang web được cài đặt làm PWA",
- "undo-timeout": "Hoàn tác thời gian chờ",
- "undo-timeout-help": "Một số thao tác như chuyển chủ đề sẽ cho phép người kiểm duyệt hoàn tác hành động của họ trong một khung thời gian nhất định. Đặt thành 0 để tắt hoàn toàn hoàn tác.",
+ "undo-timeout": "Thời Gian Chờ Hoàn Tác",
+ "undo-timeout-help": "Một số thao tác như chuyển chủ đề sẽ cho phép mod hoàn tác hành động của họ trong khung giờ nhất định. Đặt thành 0 để tắt hoàn toàn hoàn tác.",
"topic-tools": "Công cụ chủ đề"
}
diff --git a/public/language/vi/admin/settings/homepage.json b/public/language/vi/admin/settings/homepage.json
index d4a3e81eb4..17426f63d2 100644
--- a/public/language/vi/admin/settings/homepage.json
+++ b/public/language/vi/admin/settings/homepage.json
@@ -1,8 +1,8 @@
{
"home-page": "Trang Chủ",
- "description": "Chọn trang hiển thị khi người dùng được chuyển hướng đến URL gốc diễn đàn của bạn.",
+ "description": "Chọn trang hiển thị khi người dùng chuyển hướng đến URL gốc diễn đàn của bạn.",
"home-page-route": "Liên Kết Trang Chủ",
"custom-route": "Tùy Chỉnh Liên Kết",
"allow-user-home-pages": "Cho Phép Trang Chủ Người Dùng",
- "home-page-title": "Tiêu đề của trang chủ (mặc định là \"Trang chủ\")"
+ "home-page-title": "Tiêu đề trang chủ (mặc định là \"Trang chủ\")"
}
\ No newline at end of file
diff --git a/public/language/vi/admin/settings/languages.json b/public/language/vi/admin/settings/languages.json
index c29ac40e9e..bca038be61 100644
--- a/public/language/vi/admin/settings/languages.json
+++ b/public/language/vi/admin/settings/languages.json
@@ -2,5 +2,5 @@
"language-settings": "Cài Đặt Ngôn Ngữ",
"description": "Ngôn ngữ mặc định là cài đặt ngôn ngữ cho tất cả người dùng diễn đàn của bạn. Người dùng cá nhân có thể thay đổi ngôn ngữ họ thích trong cài đặt tài khoản",
"default-language": "Ngôn Ngữ Mặc Định",
- "auto-detect": "Tự Động Phát Hiện Cài Đặt Ngôn Ngữ Cho Khách"
+ "auto-detect": "Tự Phát Hiện Cài Đặt Ngôn Ngữ Cho Khách"
}
\ No newline at end of file
diff --git a/public/language/vi/admin/settings/reputation.json b/public/language/vi/admin/settings/reputation.json
index 38afb67bf7..4914fb3a8b 100644
--- a/public/language/vi/admin/settings/reputation.json
+++ b/public/language/vi/admin/settings/reputation.json
@@ -5,11 +5,11 @@
"upvote-visibility": "Khả năng hiển thị số ủng hộ",
"upvote-visibility-all": "Mọi người có thể xem số ủng hộ",
"upvote-visibility-loggedin": "Chỉ người dùng đã đăng nhập mới có thể xem số ủng hộ",
- "upvote-visibility-privileged": "Chỉ những người dùng đặc quyền như quản trị viên và người kiểm duyệt mới có thể số ủng hộ",
+ "upvote-visibility-privileged": "Chỉ những ai có đặc quyền như admin và mod được xem số ủng hộ",
"downvote-visibility": "Khả năng hiển thị số phản đối",
"downvote-visibility-all": "Mọi người có thể xem số phản đối",
- "downvote-visibility-loggedin": "Chỉ người dùng đã đăng nhập có thể xem số phản đối",
- "downvote-visibility-privileged": "Chỉ người dùng đặc quyền như quản trị viên hoặc người điều hành có thể xem số phản đối",
+ "downvote-visibility-loggedin": "Chỉ ai đã đăng nhập được xem số phản đối",
+ "downvote-visibility-privileged": "Chỉ người dùng đặc quyền như admin hoặc mod có thể xem số phản đối",
"thresholds": "Ngưỡng hoạt động",
"min-rep-upvote": "Uy tín tối thiểu để ủng hộ bài đăng",
"upvotes-per-day": "Số phiếu ủng hộ mỗi ngày (đặt là 0 để không giới hạn)",
diff --git a/public/language/vi/admin/settings/user.json b/public/language/vi/admin/settings/user.json
index f506aa8368..ca402454fc 100644
--- a/public/language/vi/admin/settings/user.json
+++ b/public/language/vi/admin/settings/user.json
@@ -32,18 +32,18 @@
"session-duration": "Thời lượng phiên nếu \"Ghi nhớ tôi\" không được chọn (giây)",
"session-duration-help": "Theo mặc định — hoặc nếu đặt thành 0 — người dùng sẽ duy trì trạng thái đăng nhập trong suốt thời gian của phiên (VD: cửa sổ/tab trình duyệt vẫn mở trong bao lâu). Đặt giá trị này để vô hiệu hóa rõ ràng phiên sau số giây đã chỉ định.",
"online-cutoff": "Số phút sau khi người dùng được coi là không hoạt động",
- "online-cutoff-help": "Nếu người dùng không thao tác trong khoảng thời gian này, được coi là không hoạt động và không nhận được cập nhật theo thời gian thực.",
+ "online-cutoff-help": "Nếu người dùng không thao tác trong thời gian này, sẽ coi là không hoạt động và không cập nhật thời gian thực.",
"registration": "Đăng Ký Người Dùng",
- "registration-type": "Loại Đăng Ký",
- "registration-approval-type": "Loại Xét Duyệt Đăng Ký",
+ "registration-type": "Kiểu Đăng Ký",
+ "registration-approval-type": "Kiểu Xét Duyệt Đăng Ký",
"registration-type.normal": "Bình thường",
- "registration-type.admin-approval": "Quản Trị Viên Phê Duyệt",
- "registration-type.admin-approval-ip": "Quản Trị Viên Phê Duyệt cho IP",
+ "registration-type.admin-approval": "Admin Phê Duyệt",
+ "registration-type.admin-approval-ip": "Amin Duyệt IP",
"registration-type.invite-only": "Chỉ Mời",
- "registration-type.admin-invite-only": "Chỉ Quản Trị Viên Mời",
- "registration-type.disabled": "Không có đăng ký",
- "registration-type.help": "Bình thường - Người dùng có thể đăng ký từ trang /register. \nChỉ mời - Người dùng có thể mời những người khác từ trang người dùng. \nChỉ Quản Trị Viên mời - Chỉ quản trị viên mới có thể mời người khác từ trang người dùng và admin/manage/users. \nKhông đăng ký - Không đăng ký người dùng. ",
- "registration-approval-type.help": "Bình thường - Người dùng được đăng ký ngay lập tức. \nPhê duyệt của quản trị viên - Đăng ký người dùng được đặt trong xếp hàng phê duyệt cho quản trị viên. \nPhê duyệt của quản trị viên cho các IP - Bình thường cho người dùng mới, Phê duyệt quản trị cho các địa chỉ IP đã có tài khoản. ",
+ "registration-type.admin-invite-only": "Chỉ Admin Mời",
+ "registration-type.disabled": "Không đăng ký",
+ "registration-type.help": "Bình Thường - Người dùng có thể đăng ký từ trang /register. \nChỉ Mời - Người dùng có thể mời những người khác từ trang người dùng. \nChỉ Admin Mời - Chỉ quản trị viên mới có thể mời người khác từ trang người dùng và admin/manage/users. \nKhông đăng ký - Không đăng ký người dùng. ",
+ "registration-approval-type.help": "Bình thường - Người dùng được đăng ký ngay lập tức. \nAdmin phê duyệt - Đăng ký người dùng được đặt trong hàng phê duyệt cho quản trị viên. \nAdmin phê duyệt IP - Bình thường cho người mới, Admin phê duyệt IP đã có tài khoản. ",
"registration-queue-auto-approve-time": "Thời Gian Xét Duyệt Tự Động",
"registration-queue-auto-approve-time-help": "Giờ trước khi người dùng được xét duyệt tự động. 0 để tắt.",
"registration-queue-show-average-time": "Hiện thời gian xét duyệt cho người dùng mới biết",
@@ -58,7 +58,7 @@
"min-password-strength": "Độ Mạnh Mật Khẩu Tối Thiểu",
"max-about-me-length": "Độ Dài Tối Đa Giới Thiệu Bản Thân",
"terms-of-use": "Điều Khoản Sử Dụng Diễn Đàn (Để trống để tắt)",
- "user-search": "Tìm Kiếm Người Dùng",
+ "user-search": "Tìm Người Dùng",
"user-search-results-per-page": "Số lượng người dùng hiển thị trong kết quả tìm kiếm",
"default-user-settings": "Cài Đặt Người Dùng Mặc Định",
"show-email": "Hiển thị email",
@@ -80,7 +80,7 @@
"default-notification-settings": "Cài đặt thông báo mặc định",
"categoryWatchState": "Trạng thái xem chuyên mục mặc định",
"categoryWatchState.tracking": "Theo dõi",
- "categoryWatchState.notwatching": "Không Xem",
+ "categoryWatchState.notwatching": "Chưa Xem",
"categoryWatchState.ignoring": "Bỏ Qua",
"restrictions-new": "Hạn chế người dùng mới",
"restrictions.rep-threshold": "Ngưỡng uy tín trước khi những hạn chế này được dỡ bỏ",
diff --git a/public/language/vi/category.json b/public/language/vi/category.json
index 830edf87b9..8cf9e53d07 100644
--- a/public/language/vi/category.json
+++ b/public/language/vi/category.json
@@ -11,7 +11,7 @@
"ignore": "Bỏ qua",
"watching": "Đang xem",
"tracking": "Theo dõi",
- "not-watching": "Không xem",
+ "not-watching": "Chưa Xem",
"ignoring": "Bỏ qua",
"watching.description": "Thông báo tôi chủ đề mới. Hiển thị chủ đề chưa đọc và gần đây",
"tracking.description": "Hiển thị chủ đề chưa đọc và gần đây",
@@ -21,6 +21,6 @@
"tracking.message": "Bạn hiện đang theo dõi thông tin cập nhật từ danh mục này và tất cả các danh mục phụ",
"notwatching.message": "Bạn không xem cập nhật từ danh mục này và tất cả các danh mục phụ",
"ignoring.message": "Bây giờ bạn đang bỏ qua các cập nhật từ danh mục này và tất cả các danh mục phụ",
- "watched-categories": "Chuyên mục đã xem",
+ "watched-categories": "Danh mục đã xem",
"x-more-categories": "%1 chuyên mục khác"
}
\ No newline at end of file
diff --git a/public/language/vi/email.json b/public/language/vi/email.json
index 0e5db90609..0f3b683b6f 100644
--- a/public/language/vi/email.json
+++ b/public/language/vi/email.json
@@ -3,8 +3,8 @@
"password-reset-requested": "Yêu cầu đặt lại mật khẩu!",
"welcome-to": "Chào mừng đến với %1",
"invite": "Lời mời từ %1",
- "greeting-no-name": "Xin chào",
- "greeting-with-name": "Xin chào %1",
+ "greeting-no-name": "Chào",
+ "greeting-with-name": "Chào %1",
"email.verify-your-email.subject": "Vui lòng xác thực tài khoản của bạn",
"email.verify.text1": "Bạn đã yêu cầu chúng tôi thay đổi hoặc xác nhận địa chỉ email của bạn",
"email.verify.text2": "Vì lý do bảo mật, chúng tôi chỉ thay đổi hoặc xác nhận địa chỉ email trong hồ sơ khi quyền sở hữu của nó đã được xác nhận qua email. Nếu bạn không yêu cầu điều này, bạn không cần thực hiện hành động nào.",
@@ -36,8 +36,8 @@
"digest.title.day": "Thông Báo Hàng Ngày Của Bạn",
"digest.title.week": "Thông Báo Hàng Tuần Của Bạn",
"digest.title.month": "Thông Báo Hàng Tháng Của Bạn",
- "notif.chat.new-message-from-user": "New message from \"%1\"",
- "notif.chat.new-message-from-user-in-room": "New message from %1 in room %2",
+ "notif.chat.new-message-from-user": "Tin nhắn mới từ \"%1\"",
+ "notif.chat.new-message-from-user-in-room": "Tin nhắn mới từ %1 trong phòng %2",
"notif.chat.cta": "Nhấn vào đây để tiếp tục cuộc hội thoại",
"notif.chat.unsub.info": "Thông báo trò chuyện này đã được gửi cho bạn dựa theo cài đặt đăng ký của bạn.",
"notif.post.unsub.info": "Thông báo bài viết này được gửi cho bạn dựa tên thiết lập nhận thông báo của bạn",
@@ -57,5 +57,5 @@
"banned.text1": "Người dùng %1 đã bị cấm khỏi %2",
"banned.text2": "Lệnh cấm sẽ kéo dài đến %1.",
"banned.text3": "Đây là lý do tại sao bạn bị cấm:",
- "closing": "Xin cảm ơn!"
+ "closing": "Cảm ơn!"
}
\ No newline at end of file
diff --git a/public/language/vi/error.json b/public/language/vi/error.json
index 4a777b5862..ca97fbbf66 100644
--- a/public/language/vi/error.json
+++ b/public/language/vi/error.json
@@ -91,17 +91,17 @@
"category-not-selected": "Danh mục không được chọn.",
"too-many-posts": "Bạn chỉ có đăng bài mới mỗi %1 giây - vui lòng đợi để tiếp tục đăng bài.",
"too-many-posts-newbie": "Là người dùng mới, bạn chỉ có thể đăng %1 giây một lần cho đến khi bạn đạt được %2 danh tiếng - vui lòng đợi trước khi đăng lại",
- "too-many-posts-newbie-minutes": "Là người dùng mới, bạn chỉ có thể đăng bài %1 phút một lần cho đến khi bạn đạt được %2 danh tiếng - vui lòng đợi trước khi đăng lại",
- "already-posting": "You are already posting",
+ "too-many-posts-newbie-minutes": "Là người dùng mới, bạn chỉ được đăng bài %1 phút một lần cho đến khi bạn đạt được %2 danh tiếng - vui lòng đợi trước khi đăng lại",
+ "already-posting": "Bạn đã đăng rồi",
"tag-too-short": "Vui lòng nhập tag dài hơn. Tag phải có tối thiểu %1 ký tự.",
"tag-too-long": "Vui lòng nhập tag ngắn hơn. Tag chỉ có thể có tối đa %1 ký tự.",
- "tag-not-allowed": "Thẻ không được phép",
+ "tag-not-allowed": "Thẻ không cho phép",
"not-enough-tags": "Không đủ thẻ. Chủ đề phải có ít nhất %1 thẻ.",
"too-many-tags": "Quá nhiều thẻ. Chủ đề không thể nhiều hơn %1 thẻ.",
"cant-use-system-tag": "Bạn không thể dùng thẻ hệ thống này.",
"cant-remove-system-tag": "Bạn không thể xóa thẻ hệ thống này.",
"still-uploading": "Vui lòng đợi quá trình tải lên hoàn tất.",
- "file-too-big": "Kích thước tệp cho phép tối đa là %1 kB - vui lòng tải lên một tệp nhỏ hơn",
+ "file-too-big": "Kích thước tối đa là %1 kB - vui lòng tải lên một tệp nhỏ hơn",
"guest-upload-disabled": "Tải lên của khách đã bị tắt",
"cors-error": "Không thể tải lên hình ảnh do CORS bị cấu hình sai",
"upload-ratelimit-reached": "Bạn đã tải lên quá nhiều tệp cùng một lúc. Vui lòng thử lại sau.",
@@ -168,12 +168,12 @@
"cant-remove-users-from-chat-room": "Không thể xóa người dùng khỏi phòng trò chuyện.",
"chat-room-name-too-long": "Tên phòng trò chuyện quá dài. Tên không được dài hơn %1 ký tự.",
"already-voting-for-this-post": "Bạn đã bỏ phiếu cho bài viết này",
- "reputation-system-disabled": "Hệ thống đánh giá uy tính đã bị vô hiệu hóa.",
+ "reputation-system-disabled": "Hệ thống đánh giá uy tính đã tắt.",
"downvoting-disabled": "Phản đối đã bị tắt",
"not-enough-reputation-to-chat": "Bạn cần %1 uy tín để trò chuyện",
"not-enough-reputation-to-upvote": "Bạn cần %1 uy tín để ủng hộ",
"not-enough-reputation-to-downvote": "Bạn cần %1 uy tín để phản đối",
- "not-enough-reputation-to-post-links": "You need %1 reputation to post links",
+ "not-enough-reputation-to-post-links": "Bạn cần %1 uy tín để đăng liên kết",
"not-enough-reputation-to-flag": "Bạn cần %1 uy tín để gắn cờ bài đăng này",
"not-enough-reputation-min-rep-website": "Bạn cần %1 uy tín để thêm một trang web",
"not-enough-reputation-min-rep-aboutme": "Bạn cần %1 uy tín để thêm thông tin bản thân",
@@ -184,15 +184,15 @@
"user-already-flagged": "Bạn đã gắn cờ người dùng này",
"post-flagged-too-many-times": "Bài đăng này đã bị người khác gắn cờ",
"user-flagged-too-many-times": "Người dùng này đã bị người khác gắn cờ",
- "too-many-post-flags-per-day": "Bạn chỉ có thể gắn cờ %1 bài đăng mỗi ngày",
- "too-many-user-flags-per-day": "Bạn chỉ có thể gắn cờ %1 người dùng mỗi ngày",
- "cant-flag-privileged": "Bạn không có quyền gắn cờ hồ sơ hay nội dung của người dùng đặc quyền (người kiểm duyệt/người điều hành chung/quản trị viên)",
+ "too-many-post-flags-per-day": "Bạn chỉ được gắn cờ %1 bài đăng mỗi ngày",
+ "too-many-user-flags-per-day": "Bạn chỉ được gắn cờ %1 người dùng mỗi ngày",
+ "cant-flag-privileged": "Bạn không có quyền gắn cờ hồ sơ hay nội dung của người dùng đặc quyền (mod/người điều hành chung/admin)",
"cant-locate-flag-report": "Không thể định vị báo cáo cờ",
"self-vote": "Bạn không thể tự bầu cho bài đăng của mình",
"too-many-upvotes-today": "Bạn chỉ có thể ủng hộ %1 lần một ngày",
- "too-many-upvotes-today-user": "Bạn chỉ có thể ủng hộ người dùng %1 lần một ngày",
+ "too-many-upvotes-today-user": "Bạn chỉ được ủng hộ người dùng %1 lần một ngày",
"too-many-downvotes-today": "Bạn chỉ có thể phản đối %1 lần một ngày",
- "too-many-downvotes-today-user": "Bạn chỉ có thể phản đối người dùng %1 lần một ngày",
+ "too-many-downvotes-today-user": "Bạn chỉ được phản đối người dùng %1 lần một ngày",
"reload-failed": "NodeBB gặp lỗi trong khi tải lại: \"%1\". NodeBB sẽ tiếp tục hoạt động với dữ liệu trước đó, tuy nhiên bạn nên tháo gỡ những gì bạn vừa thực hiện trước khi tải lại.",
"registration-error": "Lỗi Đăng Ký",
"parse-error": "Đã xảy ra lỗi khi phân tích phản hồi của máy chủ",
@@ -213,9 +213,9 @@
"session-mismatch-text": "Có vẻ như phiên đăng nhập của bạn không còn khớp với máy chủ. Vui lòng làm mới trang này.",
"no-topics-selected": "Không có chủ đề nào đang được chọn!",
"cant-move-to-same-topic": "Bạn không thể di chuyển bài viết vào cùng chủ đề hiện tại!",
- "cant-move-topic-to-same-category": "Không thể di chuyển chủ đề sang cùng chuyên mục!",
- "cannot-block-self": "Bạn không thể tự khóa tài khoản của bạn!",
- "cannot-block-privileged": "Bạn không thể khóa quản trị viên hay người điều hành chung.",
+ "cant-move-topic-to-same-category": "Không thể di chuyển chủ đề đến cùng danh mục!",
+ "cannot-block-self": "Bạn không thể tự khóa bạn!",
+ "cannot-block-privileged": "Bạn không thể admin hay người điều hành chung.",
"cannot-block-guest": "Khách không thể chặn người dùng khác",
"already-blocked": "Người dùng này đã bị chặn",
"already-unblocked": "Người dùng này đã được bỏ chặn",
diff --git a/public/language/vi/flags.json b/public/language/vi/flags.json
index 599af21987..139d0bf25f 100644
--- a/public/language/vi/flags.json
+++ b/public/language/vi/flags.json
@@ -5,7 +5,7 @@
"first-reported": "Được báo cáo đầu tiên",
"no-flags": "Hoan hô! Không tìm thấy cờ.",
"x-flags-found": "%1 cờ tìm thấy.",
- "assignee": "Người được ủy nhiệm",
+ "assignee": "Ủy nhiệm",
"update": "Cập nhật",
"updated": "Đã cập nhật",
"resolved": "Đã Xử Lý",
@@ -19,14 +19,14 @@
"filter-active": "Có một hoặc nhiều bộ lọc đang hoạt động trong danh sách cờ này",
"filter-reset": "Xóa Bộ Lọc",
"filters": "Tùy Chọn Bộ Lọc",
- "filter-reporterId": "Phóng viên",
- "filter-targetUid": "Reportee",
+ "filter-reporterId": "Báo cáo viên",
+ "filter-targetUid": "Người cáo cáo",
"filter-type": "Loại Cờ",
"filter-type-all": "Tất Cả Nội Dung",
"filter-type-post": "Bài viết",
"filter-type-user": "Người dùng",
"filter-state": "Trạng thái",
- "filter-assignee": "Assignee",
+ "filter-assignee": "Ủy nhiệm",
"filter-cid": "Chuyên mục",
"filter-quick-mine": "Được giao cho tôi",
"filter-cid-all": "Tất cả chuyên mục",
diff --git a/public/language/vi/global.json b/public/language/vi/global.json
index 4425e7821f..53ff511ca8 100644
--- a/public/language/vi/global.json
+++ b/public/language/vi/global.json
@@ -144,7 +144,7 @@
"copied": "Đã sao chép",
"user-search-prompt": "Nhập để tìm kiếm thành viên",
"hidden": "Ẩn",
- "sort": "Sort",
+ "sort": "Xếp",
"actions": "Hành Động",
"rss-feed": "Nguồn RSS",
"skip-to-content": "Bỏ qua nội dung"
diff --git a/public/language/vi/modules.json b/public/language/vi/modules.json
index f4f1f9dc90..196f087d6e 100644
--- a/public/language/vi/modules.json
+++ b/public/language/vi/modules.json
@@ -1,12 +1,13 @@
{
- "chat.room-id": "Room %1",
+ "chat.room-id": "Phòng %1",
"chat.chatting-with": "Trò chuyện với",
- "chat.placeholder": "Nhập tin nhắn trò chuyện tại đây, kéo và thả hình ảnh, nhấn enter để gửi",
- "chat.placeholder.mobile": "Nhập tin nhắn trò chuyện tại đây",
+ "chat.placeholder": "Type chat message here, drag & drop images",
+ "chat.placeholder.mobile": "Type chat message",
+ "chat.placeholder.message-room": "Message #%1",
"chat.scroll-up-alert": "Đi đến tin nhắn gần đây nhất",
- "chat.usernames-and-x-others": "%1 & %2 others",
+ "chat.usernames-and-x-others": "%1 & %2 khác",
"chat.chat-with-usernames": "Trò chuyện với %1",
- "chat.chat-with-usernames-and-x-others": "Chat with %1 & %2 others",
+ "chat.chat-with-usernames-and-x-others": "Trò chuyện với %1 & %2 khác",
"chat.send": "Gửi",
"chat.no-active": "Bạn không có cuộc trò chuyện đang hoạt động nào.",
"chat.user-typing-1": "%1 đang viết ...",
@@ -54,7 +55,7 @@
"chat.select-groups": "Chọn Nhóm",
"chat.add-user-help": "Tìm người dùng ở đây. Người dùng được chọn sẽ được thêm vào trò chuyện. Người dùng mới vào sẽ không thấy tin nhắn trò chuyện được đăng trước khi họ được thêm vào. Chỉ chủ phòng () được xóa người dùng khỏi phòng trò chuyện.",
"chat.confirm-chat-with-dnd-user": "Người dùng này đã đặt trạng thái của họ thành DnD (Không làm phiền). Bạn vẫn muốn trò chuyện với họ?",
- "chat.room-name-optional": "Room Name (Optional)",
+ "chat.room-name-optional": "Tên Phòng (Tùy Chọn)",
"chat.rename-room": "Đổi Tên Phòng",
"chat.rename-placeholder": "Nhập tên phòng của bạn ở đây",
"chat.rename-help": "Đẳt tên phòng ở đây, tất cả những người tham gia phòng này có thể xem.",
diff --git a/public/language/vi/pages.json b/public/language/vi/pages.json
index 7a8105c4e6..671b600111 100644
--- a/public/language/vi/pages.json
+++ b/public/language/vi/pages.json
@@ -47,11 +47,11 @@
"account/latest-posts": "Bài viết mới nhất do %1",
"account/topics": "Chủ đề được tạo bởi %1",
"account/groups": "Nhóm của %1",
- "account/watched-categories": "Chuyên Mục Đã Xem Của %1",
+ "account/watched-categories": "Danh Mục Đã Xem Của %1",
"account/watched-tags": "%1's Thẻ Đã Xem",
"account/bookmarks": "Bài Đăng Được Đánh Dấu Trang Của %1",
"account/settings": "Cài Đặt Người Dùng",
- "account/settings-of": "Changing settings of %1",
+ "account/settings-of": "Thay đổi cài đặt của %1",
"account/watched": "Chủ đề đã được %1 xem",
"account/ignored": "Các chủ đề bị bỏ qua bởi %1",
"account/upvoted": "Bài đăng được %1 ủng hộ",
diff --git a/public/language/vi/post-queue.json b/public/language/vi/post-queue.json
index e75599fe72..3192e1c1a0 100644
--- a/public/language/vi/post-queue.json
+++ b/public/language/vi/post-queue.json
@@ -8,7 +8,7 @@
"public-intro": "Nếu bạn có bất kỳ bài đăng nào được xếp hàng đợi, chúng sẽ được hiển thị ở đây.",
"public-description": "Diễn đàn này được cấu hình tự động xếp hàng các bài đăng từ tài khoản mới, chờ người điều hành phê duyệt. Nếu bạn đã xếp hàng các bài đăng đợi phê duyệt, bạn sẽ có thể xem chúng ở đây.",
"user": "Người dùng",
- "when": "When",
+ "when": "Khi",
"category": "Chuyên mục",
"title": "Tiêu đề",
"content": "Nội dung",
diff --git a/public/language/vi/recent.json b/public/language/vi/recent.json
index 353a0ae2d4..3fdb8b79e1 100644
--- a/public/language/vi/recent.json
+++ b/public/language/vi/recent.json
@@ -7,5 +7,5 @@
"alltime": "Mọi Lúc",
"no-recent-topics": "Không có chủ đề gần đây.",
"no-popular-topics": "Không có chủ đề nào phổ biến.",
- "load-new-posts": "Load new posts"
+ "load-new-posts": "Tải bài đăng mới"
}
\ No newline at end of file
diff --git a/public/language/vi/register.json b/public/language/vi/register.json
index 0018ecbecc..e7f1e0afb6 100644
--- a/public/language/vi/register.json
+++ b/public/language/vi/register.json
@@ -13,7 +13,7 @@
"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",
+ "register-now-button": "Đăng Ký Ngay",
"alternative-registration": "Đăng Ký Thay Thế",
"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",
diff --git a/public/language/vi/search.json b/public/language/vi/search.json
index 4731b62121..3161819996 100644
--- a/public/language/vi/search.json
+++ b/public/language/vi/search.json
@@ -28,10 +28,10 @@
"posted-by-usernames": "Đăng bởi: %1",
"type-a-username": "Nhập tên người dùng",
"search-child-categories": "Tìm kiếm chuyên mục con",
- "has-tags": "Có thẻ bên trong",
+ "has-tags": "Có thẻ",
"reply-count": "Số lượt trả lời",
- "replies": "Replies",
- "replies-atleast-count": "Replies: At least %1",
+ "replies": "Trả lời",
+ "replies-atleast-count": "Trả lời: Ít nhất %1",
"replies-atmost-count": "Trả lời: Nhiều nhất là %1",
"at-least": "Tối thiểu",
"at-most": "Nhiều nhất",
diff --git a/public/language/vi/social.json b/public/language/vi/social.json
index 02b0446c1d..2a1194b3fd 100644
--- a/public/language/vi/social.json
+++ b/public/language/vi/social.json
@@ -1,11 +1,11 @@
{
- "sign-in-with-twitter": "Đăng nhập bằng Twitter",
+ "sign-in-with-twitter": "Đăng nhập với Twitter",
"sign-up-with-twitter": "Đăng ký với Twitter",
"sign-in-with-github": "Đăng nhập bằng Github",
"sign-up-with-github": "Đăng ký với Github",
"sign-in-with-google": "Đăng nhập bằng Google",
"sign-up-with-google": "Đăng ký với Google",
- "log-in-with-facebook": "Đăng nhập bằng Facebook",
+ "log-in-with-facebook": "Đăng nhập với Facebook",
"continue-with-facebook": "Tiếp tục với Facebook",
"sign-in-with-linkedin": "Đăng nhập với LinkedIn",
"sign-up-with-linkedin": "Đăng ký với LinkedIn"
diff --git a/public/language/vi/tags.json b/public/language/vi/tags.json
index 00a103bf0a..9e9f0d754f 100644
--- a/public/language/vi/tags.json
+++ b/public/language/vi/tags.json
@@ -1,5 +1,5 @@
{
- "all-tags": "Tất cả các thẻ",
+ "all-tags": "Tất cả thẻ",
"no-tag-topics": "Không có bài viết nào với thẻ này.",
"no-tags-found": "Không tìm thấy thẻ nào",
"tags": "Thẻ",
@@ -8,8 +8,8 @@
"no-tags": "Chưa có thẻ nào.",
"select-tags": "Chọn Thẻ",
"tag-whitelist": "Danh Sách Trắng Thẻ",
- "watching": "Watching",
- "not-watching": "Not Watching",
+ "watching": "Đang xem",
+ "not-watching": "Chưa xem",
"watching.description": "Thông báo tôi chủ đề mới.",
"not-watching.description": "Không thông báo tôi chủ đề mới.",
"following-tag.message": "Bây giờ bạn sẽ nhận được thông báo khi ai đó đăng chủ đề có thẻ này.",
diff --git a/public/language/vi/topic.json b/public/language/vi/topic.json
index 7577a867e7..19bc1289eb 100644
--- a/public/language/vi/topic.json
+++ b/public/language/vi/topic.json
@@ -88,7 +88,7 @@
"unwatch.title": "Ngừng xem chủ đề này",
"share-this-post": "Chia sẻ bài viết này",
"watching": "Đang xem",
- "not-watching": "Không Xem",
+ "not-watching": "Chưa Xem",
"ignoring": "Bỏ qua",
"watching.description": "Thông báo cho tôi về trả lời mới. Hiển thị chủ đề chưa đọc",
"not-watching.description": "Không thông báo tôi các trả lời mới. Hiển thị mục chưa đọc nếu chuyên mục bị bỏ qua.",
@@ -163,11 +163,11 @@
"composer.discard": "Huỷ bỏ",
"composer.submit": "Gửi",
"composer.additional-options": "Tùy chọn bổ sung",
- "composer.post-later": "Post Later",
+ "composer.post-later": "Đăng Sau",
"composer.schedule": "Lên lịch",
"composer.replying-to": "Đang trả lời %1",
"composer.new-topic": "Chủ đề mới",
- "composer.editing-in": "Editing post in %1",
+ "composer.editing-in": "Đang sửa bài đăng trong %1",
"composer.uploading": "đang tải lên...",
"composer.thumb-url-label": "Dán URL hình mô tả chủ đề",
"composer.thumb-title": "Thêm ảnh mô tả cho chủ đề này",
@@ -191,10 +191,10 @@
"stale.warning": "Chủ đề bạn đang trả lời đã khá cũ. Bạn có muốn tạo chủ đề mới, và liên kết với chủ đề hiện tại trong bài viết trả lời của bạn?",
"stale.create": "Tạo chủ đề mới",
"stale.reply-anyway": "Trả lời chủ đề này",
- "link-back": "Re: [%1](%2)",
- "diffs.title": "Lịch sử chỉnh sửa bài viết",
- "diffs.description": "Bài viết này có %1 sửa đổi. Nhấp vào một trong các bản sửa đổi bên dưới để xem nội dung bài đăng tại thời điểm đó.",
- "diffs.no-revisions-description": "Bài viết này có %1 sửa đổi",
+ "link-back": "Trả lời: [%1](%2)",
+ "diffs.title": "Lịch Sử Sửa Bài",
+ "diffs.description": "Bài này có %1 sửa đổi. Bấm vào một trong các bản sửa đổi bên dưới để xem nội dung bài tại thời điểm đó.",
+ "diffs.no-revisions-description": "Bài này có %1 sửa đổi",
"diffs.current-revision": "bản sửa đổi hiện tại",
"diffs.original-revision": "bản sửa đổi gốc",
"diffs.restore": "Khôi phục bản sửa đổi này",
diff --git a/public/language/vi/unread.json b/public/language/vi/unread.json
index 93324bab15..5d814d0526 100644
--- a/public/language/vi/unread.json
+++ b/public/language/vi/unread.json
@@ -10,7 +10,7 @@
"topics-marked-as-read.success": "Chủ đề được đánh dấu đã đọc",
"all-topics": "Tất Cả Chủ Đề",
"new-topics": "Chủ đề mới",
- "watched-topics": "Chủ đề đã xem",
- "unreplied-topics": "Chủ Đề Chưa Có Trả Lời",
+ "watched-topics": "Chủ Đề Đã Xem",
+ "unreplied-topics": "Chủ Đề Chưa Trả Lời",
"multiple-categories-selected": "Chọn Nhiều"
}
\ No newline at end of file
diff --git a/public/language/vi/uploads.json b/public/language/vi/uploads.json
index f32f535b40..49d929e52d 100644
--- a/public/language/vi/uploads.json
+++ b/public/language/vi/uploads.json
@@ -4,6 +4,6 @@
"upload-success": "Tải tệp lên thành công!",
"maximum-file-size": "Tối đa %1 kb",
"no-uploads-found": "Không có tải lên được tìm thấy",
- "public-uploads-info": "Các file tải lên được xuất bản, mọi người đều có thể xem được.",
- "private-uploads-info": "Các file tải lên được để ở chế độ bí mật, chỉ những người dùng đăng nhập mới có thể xem."
+ "public-uploads-info": "Tải lên là công khai, mọi người đều xem được.",
+ "private-uploads-info": "Tải lên là riêng tư, những ai đã đăng nhập mới có thể xem."
}
\ No newline at end of file
diff --git a/public/language/vi/user.json b/public/language/vi/user.json
index 05dc84d3a5..dd65ed7d70 100644
--- a/public/language/vi/user.json
+++ b/public/language/vi/user.json
@@ -91,7 +91,7 @@
"upload-a-picture": "Tải lên một hình ảnh",
"remove-uploaded-picture": "Xoá ảnh đã tải lên",
"upload-cover-picture": "Tải ảnh bìa lên",
- "remove-cover-picture-confirm": "Bạn có thật sự muốn xóa hình ảnh này?",
+ "remove-cover-picture-confirm": "Bạn có chắc muốn xóa ảnh bìa này?",
"crop-picture": "Cắt ảnh",
"upload-cropped-picture": "Cắt và tải lên",
"avatar-background-colour": "Màu nền ảnh đại diện",
diff --git a/public/language/zh-CN/modules.json b/public/language/zh-CN/modules.json
index ab79a34365..47d80bb820 100644
--- a/public/language/zh-CN/modules.json
+++ b/public/language/zh-CN/modules.json
@@ -1,8 +1,9 @@
{
"chat.room-id": "房间 %1",
"chat.chatting-with": "与聊天",
- "chat.placeholder": "在此输入聊天消息,或者拖入图片,按下回车键以发送",
- "chat.placeholder.mobile": "在此输入聊天信息",
+ "chat.placeholder": "Type chat message here, drag & drop images",
+ "chat.placeholder.mobile": "Type chat message",
+ "chat.placeholder.message-room": "Message #%1",
"chat.scroll-up-alert": "转到最近的信息",
"chat.usernames-and-x-others": "%1 和 %2 其他人",
"chat.chat-with-usernames": "与聊天",
diff --git a/public/language/zh-TW/modules.json b/public/language/zh-TW/modules.json
index a1ff97dea8..bb7fc2b41d 100644
--- a/public/language/zh-TW/modules.json
+++ b/public/language/zh-TW/modules.json
@@ -1,8 +1,9 @@
{
"chat.room-id": "Room %1",
"chat.chatting-with": "與聊天",
- "chat.placeholder": "Type chat message here, drag & drop images, press enter to send",
- "chat.placeholder.mobile": "Type chat message here",
+ "chat.placeholder": "Type chat message here, drag & drop images",
+ "chat.placeholder.mobile": "Type chat message",
+ "chat.placeholder.message-room": "Message #%1",
"chat.scroll-up-alert": "Go to most recent message",
"chat.usernames-and-x-others": "%1 & %2 others",
"chat.chat-with-usernames": "Chat with %1",
diff --git a/public/openapi/write.yaml b/public/openapi/write.yaml
index d56b109e15..f7012e77a8 100644
--- a/public/openapi/write.yaml
+++ b/public/openapi/write.yaml
@@ -166,6 +166,8 @@ paths:
$ref: 'write/topics/tid/read.yaml'
/topics/{tid}/bump:
$ref: 'write/topics/tid/bump.yaml'
+ /topics/{tid}/move:
+ $ref: 'write/topics/tid/move.yaml'
/tags/{tag}/follow:
$ref: 'write/tags/tag/follow.yaml'
/posts/{pid}:
diff --git a/public/openapi/write/topics/tid/move.yaml b/public/openapi/write/topics/tid/move.yaml
new file mode 100644
index 0000000000..6c8dedc11a
--- /dev/null
+++ b/public/openapi/write/topics/tid/move.yaml
@@ -0,0 +1,29 @@
+put:
+ tags:
+ - topics
+ summary: move topic to another category
+ description: |
+ This operation moved a topic from one category to another.
+
+ **Note**: This is a privileged call and can only be executed by administrators, global moderators, or the moderator for the category of the passed-in topic.
+ parameters:
+ - in: path
+ name: tid
+ schema:
+ type: string
+ required: true
+ description: a valid topic id
+ example: 1
+ responses:
+ '200':
+ description: Topic successfully moved
+ content:
+ application/json:
+ schema:
+ type: object
+ properties:
+ status:
+ $ref: ../../../components/schemas/Status.yaml#/Status
+ response:
+ type: object
+ properties: {}
\ No newline at end of file
diff --git a/public/src/client/chats.js b/public/src/client/chats.js
index abaae9f818..1570872d62 100644
--- a/public/src/client/chats.js
+++ b/public/src/client/chats.js
@@ -697,6 +697,12 @@ define('forum/chats', [
data.message.timestamp = Math.min(Date.now(), data.message.timestamp);
data.message.timestampISO = utils.toISOString(data.message.timestamp);
messages.appendChatMessage($('[component="chat/message/content"]'), data.message);
+
+ Chats.updateTeaser(data.roomId, {
+ content: utils.stripHTMLTags(utils.decodeHTMLEntities(data.message.content)),
+ user: data.message.fromUser,
+ timestampISO: data.message.timestampISO,
+ });
}
});
@@ -754,6 +760,31 @@ define('forum/chats', [
});
};
+ Chats.updateTeaser = async function (roomId, teaser) {
+ if (!ajaxify.data.template.chats || !app.user.userslug) {
+ return;
+ }
+ const roomEl = chatNavWrapper.find(`[data-roomid="${roomId}"]`);
+ if (roomEl.length) {
+ const html = await app.parseAndTranslate('partials/chats/room-teaser', {
+ teaser: teaser,
+ });
+ roomEl.find('[component="chat/room/teaser"]').html(html[0].outerHTML);
+ roomEl.find('.timeago').timeago();
+ } else {
+ const { rooms } = await api.get(`/chats`, { start: 0, perPage: 2 });
+ const room = rooms.find(r => parseInt(r.roomId, 10) === parseInt(roomId, 10));
+ if (room) {
+ const recentEl = components.get('chat/recent');
+ const html = await app.parseAndTranslate('chats', 'rooms', {
+ rooms: [room],
+ showBottomHr: true,
+ });
+ recentEl.prepend(html);
+ }
+ }
+ };
+
Chats.markChatPageElUnread = function (data) {
if (!ajaxify.data.template.chats) {
return;
diff --git a/public/src/client/header/chat.js b/public/src/client/header/chat.js
index a087a45939..d52f4c6c72 100644
--- a/public/src/client/header/chat.js
+++ b/public/src/client/header/chat.js
@@ -42,6 +42,7 @@ define('forum/header/chat', [
return;
}
chatPage.markChatPageElUnread(data);
+ chatPage.updateTeaser(data.roomId, data.teaser);
}
let { count } = await api.get('/chats/unread');
diff --git a/public/src/client/header/unread.js b/public/src/client/header/unread.js
index 0d67c22526..d7cee4f71e 100644
--- a/public/src/client/header/unread.js
+++ b/public/src/client/header/unread.js
@@ -97,14 +97,16 @@ define('forum/header/unread', ['hooks'], function (hooks) {
.toggleClass('hidden', count <= 0)
.text(count);
- // persona mobile menu uses data-content
- $('#mobile-menu [data-unread-url="' + url + '"]')
- .attr('data-content', countText);
+ if (navLink.length) {
+ // persona mobile menu uses data-content
+ $('#mobile-menu [data-unread-url="' + url + '"]')
+ .attr('data-content', countText);
- // harmony mobile unread badge, doesn't use data-content
- $('[component="unread/count"][data-unread-url="' + url + '"]')
- .toggleClass('hidden', count <= 0)
- .text(countText);
+ // harmony mobile unread badge, doesn't use data-content
+ $('[component="unread/count"][data-unread-url="' + url + '"]')
+ .toggleClass('hidden', count <= 0)
+ .text(countText);
+ }
hooks.fire('action:unread.updateCount', { url, count });
}
diff --git a/public/src/client/infinitescroll.js b/public/src/client/infinitescroll.js
index 838f164f32..eaf3e97720 100644
--- a/public/src/client/infinitescroll.js
+++ b/public/src/client/infinitescroll.js
@@ -20,8 +20,9 @@ define('forum/infinitescroll', ['hooks', 'alerts', 'api'], function (hooks, aler
}
previousScrollTop = $(window).scrollTop();
$(window).off('scroll', startScrollTimeout).on('scroll', startScrollTimeout);
-
- if ($body.height() <= $(window).height()) {
+ if ($body.height() <= $(window).height() && (
+ !ajaxify.data.hasOwnProperty('pageCount') || ajaxify.data.pageCount > 1
+ )) {
callback(1);
}
};
diff --git a/public/src/modules/chat.js b/public/src/modules/chat.js
index 0794af22a8..c48113d2f7 100644
--- a/public/src/modules/chat.js
+++ b/public/src/modules/chat.js
@@ -1,10 +1,9 @@
'use strict';
define('chat', [
- 'components', 'taskbar', 'translator', 'hooks', 'bootbox', 'alerts', 'api',
-], function (components, taskbar, translator, hooks, bootbox, alerts, api) {
+ 'components', 'taskbar', 'translator', 'hooks', 'bootbox', 'alerts', 'api', 'scrollStop',
+], function (components, taskbar, translator, hooks, bootbox, alerts, api, scrollStop) {
const module = {};
- let newMessage = false;
module.openChat = function (roomId, uid) {
if (!app.user.uid) {
@@ -182,9 +181,11 @@ define('chat', [
return;
}
if (module.modalExists(data.roomId)) {
+ const modal = module.getModal(data.roomId);
+ const newMessage = parseInt(modal.attr('new-message'), 10) === 1;
data.self = parseInt(app.user.uid, 10) === parseInt(data.fromUid, 10) ? 1 : 0;
if (!newMessage) {
- newMessage = data.self === 0;
+ modal.attr('new-message', data.self === 0 ? 1 : 0);
}
data.message.self = data.self;
data.message.timestamp = Math.min(Date.now(), data.message.timestamp);
@@ -290,11 +291,60 @@ define('chat', [
return $('#chat-modal-' + roomId).length !== 0;
};
+ module.initWidget = function (roomId, chatModal) {
+ require(['forum/chats', 'forum/chats/messages'], function (Chats, ChatsMessages) {
+ socket.emit('modules.chats.enter', roomId);
+ api.del(`/chats/${roomId}/state`, {});
+
+ chatModal.find('.timeago').timeago();
+ chatModal.find('[data-bs-toggle="tooltip"]').tooltip({
+ trigger: 'hover', container: '#content',
+ });
+ ChatsMessages.wrapImagesInLinks(chatModal.find('[component="chat/messages"] .chat-content'));
+
+ scrollStop.apply(chatModal.find('[component="chat/messages"] .chat-content'));
+
+ chatModal.on('mousemove keypress click', function () {
+ if (parseInt(chatModal.attr('new-message'), 10) === 1) {
+ api.del(`/chats/${roomId}/state`, {});
+ chatModal.removeAttr('new-message');
+ }
+ });
+
+ Chats.addActionHandlers(chatModal.find('[component="chat/message/window"]'), roomId);
+ Chats.addSendHandlers(roomId, chatModal.find('.chat-input'), chatModal.find('[data-action="send"]'));
+
+ Chats.createAutoComplete(roomId, chatModal.find('[component="chat/input"]'));
+
+ Chats.addScrollHandler(roomId, app.user.uid, chatModal.find('[component="chat/message/content"]'));
+ Chats.addScrollBottomHandler(roomId, chatModal.find('[component="chat/message/content"]'));
+ Chats.addParentHandler(chatModal.find('[component="chat/message/content"]'));
+ Chats.addCharactersLeftHandler(chatModal);
+ Chats.addTextareaResizeHandler(chatModal);
+ Chats.addTypingHandler(chatModal, roomId);
+ Chats.addIPHandler(chatModal);
+ Chats.addTooltipHandler(chatModal);
+ Chats.addUploadHandler({
+ dragDropAreaEl: chatModal.find('.modal-content'),
+ pasteEl: chatModal,
+ uploadFormEl: chatModal.find('[component="chat/upload"]'),
+ uploadBtnEl: chatModal.find('[component="chat/upload/button"]'),
+ inputEl: chatModal.find('[component="chat/input"]'),
+ });
+
+ ChatsMessages.addSocketListeners();
+
+ ChatsMessages.scrollToBottomAfterImageLoad(chatModal.find('.chat-content'));
+
+ hooks.fire('action:chat.loaded', chatModal);
+ });
+ };
+
module.createModal = function (data, callback) {
callback = callback || function () {};
require([
- 'scrollStop', 'forum/chats', 'forum/chats/messages', 'forum/chats/message-search',
- ], function (scrollStop, Chats, ChatsMessages, messageSearch) {
+ 'forum/chats', 'forum/chats/messages', 'forum/chats/message-search',
+ ], function (Chats, ChatsMessages, messageSearch) {
app.parseAndTranslate('chat', data, function (chatModal) {
const roomId = data.roomId;
if (module.modalExists(roomId)) {
@@ -305,45 +355,19 @@ define('chat', [
chatModal.attr('id', 'chat-modal-' + roomId);
chatModal.attr('data-roomid', roomId);
- chatModal.attr('intervalId', 0);
chatModal.attr('data-uuid', uuid);
chatModal.css('position', 'fixed');
chatModal.appendTo($('body'));
chatModal.find('.timeago').timeago();
chatModal.find('[data-bs-toggle="tooltip"]').tooltip({ trigger: 'hover', container: '#content' });
ChatsMessages.wrapImagesInLinks(chatModal.find('[component="chat/messages"] .chat-content'));
- module.center(chatModal);
-
- app.loadJQueryUI(function () {
- chatModal.find('.modal-content').resizable({
- handles: 'n, e, s, w, se',
- minHeight: 250,
- minWidth: 400,
- });
-
- chatModal.find('.modal-content').on('resize', function (event, ui) {
- if (ui.originalSize.height === ui.size.height) {
- return;
- }
-
- chatModal.find('.modal-body').css('height', module.calculateChatListHeight(chatModal));
- });
-
- chatModal.draggable({
- start: function () {
- taskbar.updateActive(uuid);
- chatModal.css({ bottom: 'auto', right: 'auto' });
- },
- stop: function () {
- module.focusInput(chatModal);
- },
- distance: 10,
- handle: '.modal-header',
- });
- });
scrollStop.apply(chatModal.find('[component="chat/messages"] .chat-content'));
+ module.center(chatModal);
+
+ makeModalResizeableDraggable(chatModal, uuid);
+
chatModal.find('#chat-close-btn').on('click', function () {
module.close(uuid);
});
@@ -380,9 +404,9 @@ define('chat', [
});
chatModal.on('mousemove keypress click', function () {
- if (newMessage) {
+ if (parseInt(chatModal.attr('new-message'), 10) === 1) {
api.del(`/chats/${roomId}/state`, {});
- newMessage = false;
+ chatModal.removeAttr('new-message');
}
});
@@ -433,6 +457,36 @@ define('chat', [
});
};
+ function makeModalResizeableDraggable(chatModal, uuid) {
+ app.loadJQueryUI(function () {
+ chatModal.find('.modal-content').resizable({
+ handles: 'n, e, s, w, se',
+ minHeight: 250,
+ minWidth: 400,
+ });
+
+ chatModal.find('.modal-content').on('resize', function (event, ui) {
+ if (ui.originalSize.height === ui.size.height) {
+ return;
+ }
+
+ chatModal.find('.modal-body').css('height', module.calculateChatListHeight(chatModal));
+ });
+
+ chatModal.draggable({
+ start: function () {
+ taskbar.updateActive(uuid);
+ chatModal.css({ bottom: 'auto', right: 'auto' });
+ },
+ stop: function () {
+ module.focusInput(chatModal);
+ },
+ distance: 10,
+ handle: '.modal-header',
+ });
+ });
+ }
+
module.focusInput = function (chatModal) {
setTimeout(function () {
chatModal.find('[component="chat/input"]').focus();
@@ -441,8 +495,6 @@ define('chat', [
module.close = function (uuid) {
const chatModal = $('.chat-modal[data-uuid="' + uuid + '"]');
- clearInterval(chatModal.attr('intervalId'));
- chatModal.attr('intervalId', 0);
chatModal.remove();
chatModal.data('modal', null);
taskbar.discard('chat', uuid);
@@ -529,8 +581,6 @@ define('chat', [
const chatModal = $('.chat-modal[data-uuid="' + uuid + '"]');
chatModal.addClass('hide');
taskbar.minimize('chat', uuid);
- clearInterval(chatModal.attr('intervalId'));
- chatModal.attr('intervalId', 0);
hooks.fire('action:chat.minimized', {
uuid: uuid,
modal: chatModal,
diff --git a/src/api/chats.js b/src/api/chats.js
index db07ac32f0..abd5c908f2 100644
--- a/src/api/chats.js
+++ b/src/api/chats.js
@@ -37,7 +37,7 @@ async function rateLimitExceeded(caller, field) {
}
chatsAPI.list = async (caller, { uid = caller.uid, start, stop, page, perPage } = {}) => {
- if (!start && !stop && !page) {
+ if ((!utils.isNumber(start) || !utils.isNumber(stop)) && !utils.isNumber(page)) {
throw new Error('[[error:invalid-data]]');
}
diff --git a/src/api/topics.js b/src/api/topics.js
index 21d7111c62..94b1793519 100644
--- a/src/api/topics.js
+++ b/src/api/topics.js
@@ -4,9 +4,12 @@ const validator = require('validator');
const user = require('../user');
const topics = require('../topics');
+const categories = require('../categories');
const posts = require('../posts');
const meta = require('../meta');
const privileges = require('../privileges');
+const events = require('../events');
+const batch = require('../batch');
const activitypubApi = require('./activitypub');
const apiHelpers = require('./helpers');
@@ -306,3 +309,48 @@ topicsAPI.bump = async (caller, { tid }) => {
await topics.markAsUnreadForAll(tid);
topics.pushUnreadCount(caller.uid);
};
+
+topicsAPI.move = async (caller, { tid, cid }) => {
+ const canMove = await privileges.categories.isAdminOrMod(cid, caller.uid);
+ if (!canMove) {
+ throw new Error('[[error:no-privileges]]');
+ }
+
+ const tids = Array.isArray(tid) ? tid : [tid];
+ const uids = await user.getUidsFromSet('users:online', 0, -1);
+ const cids = [parseInt(cid, 10)];
+
+ await batch.processArray(tids, async (tids) => {
+ await Promise.all(tids.map(async (tid) => {
+ const canMove = await privileges.topics.isAdminOrMod(tid, caller.uid);
+ if (!canMove) {
+ throw new Error('[[error:no-privileges]]');
+ }
+ const topicData = await topics.getTopicFields(tid, ['tid', 'cid', 'slug', 'deleted']);
+ if (!cids.includes(topicData.cid)) {
+ cids.push(topicData.cid);
+ }
+ await topics.tools.move(tid, {
+ cid,
+ uid: caller.uid,
+ });
+
+ const notifyUids = await privileges.categories.filterUids('topics:read', topicData.cid, uids);
+ socketHelpers.emitToUids('event:topic_moved', topicData, notifyUids);
+ if (!topicData.deleted) {
+ socketHelpers.sendNotificationToTopicOwner(tid, caller.uid, 'move', 'notifications:moved-your-topic');
+ }
+
+ await events.log({
+ type: `topic-move`,
+ uid: caller.uid,
+ ip: caller.ip,
+ tid: tid,
+ fromCid: topicData.cid,
+ toCid: cid,
+ });
+ }));
+ }, { batch: 10 });
+
+ await categories.onTopicsMoved(cids);
+};
diff --git a/src/controllers/write/topics.js b/src/controllers/write/topics.js
index b9691a8da5..cafcec2f7f 100644
--- a/src/controllers/write/topics.js
+++ b/src/controllers/write/topics.js
@@ -207,3 +207,10 @@ Topics.bump = async (req, res) => {
helpers.formatApiResponse(200, res);
};
+
+Topics.move = async (req, res) => {
+ const { cid } = req.body;
+ await api.topics.move(req, { cid, ...req.params });
+
+ helpers.formatApiResponse(200, res);
+};
diff --git a/src/events.js b/src/events.js
index 41e1f0d29b..d7938e3b5a 100644
--- a/src/events.js
+++ b/src/events.js
@@ -75,6 +75,7 @@ events.types = [
'export:uploads',
'account-locked',
'getUsersCSV',
+ 'getGroupCSV',
'chat-room-deleted',
// To add new types from plugins, just Array.push() to this array
];
diff --git a/src/messaging/notifications.js b/src/messaging/notifications.js
index 503382cf01..d939bc939b 100644
--- a/src/messaging/notifications.js
+++ b/src/messaging/notifications.js
@@ -1,6 +1,7 @@
'use strict';
const winston = require('winston');
+const validator = require('validator');
const batch = require('../batch');
const db = require('../database');
@@ -8,6 +9,7 @@ const notifications = require('../notifications');
const user = require('../user');
const io = require('../socket.io');
const plugins = require('../plugins');
+const utils = require('../utils');
module.exports = function (Messaging) {
Messaging.setUserNotificationSetting = async (uid, roomId, value) => {
@@ -66,6 +68,13 @@ module.exports = function (Messaging) {
// push unread count only for private rooms
if (!isPublic) {
const uids = await Messaging.getAllUidsInRoomFromSet(`chat:room:${roomId}:uids:online`);
+ unreadData.teaser = {
+ content: validator.escape(
+ String(utils.stripHTMLTags(utils.decodeHTMLEntities(messageObj.content)))
+ ),
+ user: messageObj.fromUser,
+ timestampISO: messageObj.timestampISO,
+ };
Messaging.pushUnreadCount(uids, unreadData);
}
diff --git a/src/plugins/index.js b/src/plugins/index.js
index f3a42aa01a..37980dd55c 100644
--- a/src/plugins/index.js
+++ b/src/plugins/index.js
@@ -155,6 +155,7 @@ Plugins.get = async function (id) {
const url = `${nconf.get('registry') || 'https://packages.nodebb.org'}/api/v1/plugins/${id}`;
const { response, body } = await request.get(url);
if (!response.ok) {
+ console.log(response);
throw new Error(`[[error:unable-to-load-plugin, ${id}]]`);
}
let normalised = await Plugins.normalise([body ? body.payload : {}]);
@@ -171,6 +172,7 @@ Plugins.list = async function (matching) {
try {
const { response, body } = await request.get(url);
if (!response.ok) {
+ console.log(response);
throw new Error(`[[error:unable-to-load-plugins-from-nbbpm]]`);
}
return await Plugins.normalise(body);
@@ -184,6 +186,7 @@ Plugins.listTrending = async () => {
const url = `${nconf.get('registry') || 'https://packages.nodebb.org'}/api/v1/analytics/top/week`;
const { response, body } = await request.get(url);
if (!response.ok) {
+ console.log(response);
throw new Error(`[[error:unable-to-load-trending-plugins]]`);
}
return body;
diff --git a/src/posts/parse.js b/src/posts/parse.js
index add64ac4d7..fcc87b1ecd 100644
--- a/src/posts/parse.js
+++ b/src/posts/parse.js
@@ -22,7 +22,7 @@ let sanitizeConfig = {
...sanitize.defaults.allowedAttributes,
a: ['href', 'name', 'hreflang', 'media', 'rel', 'target', 'type'],
img: ['alt', 'height', 'ismap', 'src', 'usemap', 'width', 'srcset'],
- iframe: ['height', 'name', 'src', 'width'],
+ iframe: ['height', 'name', 'src', 'width', 'allow', 'frameborder'],
video: ['autoplay', 'playsinline', 'controls', 'height', 'loop', 'muted', 'poster', 'preload', 'src', 'width'],
audio: ['autoplay', 'controls', 'loop', 'muted', 'preload', 'src'],
source: ['type', 'src', 'srcset', 'sizes', 'media', 'height', 'width'],
diff --git a/src/routes/write/topics.js b/src/routes/write/topics.js
index 1a537fd56d..df10f66633 100644
--- a/src/routes/write/topics.js
+++ b/src/routes/write/topics.js
@@ -49,5 +49,7 @@ module.exports = function () {
setupApiRoute(router, 'delete', '/:tid/read', [...middlewares, middleware.assert.topic], controllers.write.topics.markUnread);
setupApiRoute(router, 'put', '/:tid/bump', [...middlewares, middleware.assert.topic], controllers.write.topics.bump);
+ setupApiRoute(router, 'put', '/:tid/move', [...middlewares, middleware.assert.topic], controllers.write.topics.move);
+
return router;
};
diff --git a/src/socket.io/meta.js b/src/socket.io/meta.js
index f150102f13..0e59e93849 100644
--- a/src/socket.io/meta.js
+++ b/src/socket.io/meta.js
@@ -5,6 +5,7 @@ const os = require('os');
const user = require('../user');
const meta = require('../meta');
const topics = require('../topics');
+const privileges = require('../privileges');
const SocketMeta = module.exports;
SocketMeta.rooms = {};
@@ -44,6 +45,20 @@ SocketMeta.rooms.enter = async function (socket, data) {
throw new Error('[[error:not-allowed]]');
}
+ if (data.enter && data.enter.startsWith('topic_')) {
+ const tid = data.enter.split('_').pop();
+ if (!await privileges.topics.can('topics:read', tid, socket.uid)) {
+ throw new Error('[[error:no-privileges]]');
+ }
+ }
+
+ if (data.enter && data.enter.startsWith('category_')) {
+ const cid = data.enter.split('_').pop();
+ if (!await privileges.categories.can('read', cid, socket.uid)) {
+ throw new Error('[[error:no-privileges]]');
+ }
+ }
+
leaveCurrentRoom(socket);
if (data.enter) {
diff --git a/src/socket.io/topics/move.js b/src/socket.io/topics/move.js
index 6c03412cc2..edda6b2c77 100644
--- a/src/socket.io/topics/move.js
+++ b/src/socket.io/topics/move.js
@@ -1,55 +1,26 @@
'use strict';
const async = require('async');
-const user = require('../../user');
const topics = require('../../topics');
const categories = require('../../categories');
const privileges = require('../../privileges');
-const socketHelpers = require('../helpers');
const events = require('../../events');
+const api = require('../../api');
+const sockets = require('..');
+
module.exports = function (SocketTopics) {
SocketTopics.move = async function (socket, data) {
+ sockets.warnDeprecated(socket, 'GET /api/v3/topics/:tid/move');
+
if (!data || !Array.isArray(data.tids) || !data.cid) {
throw new Error('[[error:invalid-data]]');
}
- const canMove = await privileges.categories.isAdminOrMod(data.cid, socket.uid);
- if (!canMove) {
- throw new Error('[[error:no-privileges]]');
- }
-
- const uids = await user.getUidsFromSet('users:online', 0, -1);
- const cids = [parseInt(data.cid, 10)];
- await async.eachLimit(data.tids, 10, async (tid) => {
- const canMove = await privileges.topics.isAdminOrMod(tid, socket.uid);
- if (!canMove) {
- throw new Error('[[error:no-privileges]]');
- }
- const topicData = await topics.getTopicFields(tid, ['tid', 'cid', 'slug', 'deleted']);
- if (!cids.includes(topicData.cid)) {
- cids.push(topicData.cid);
- }
- data.uid = socket.uid;
- await topics.tools.move(tid, data);
-
- const notifyUids = await privileges.categories.filterUids('topics:read', topicData.cid, uids);
- socketHelpers.emitToUids('event:topic_moved', topicData, notifyUids);
- if (!topicData.deleted) {
- socketHelpers.sendNotificationToTopicOwner(tid, socket.uid, 'move', 'notifications:moved-your-topic');
- }
-
- await events.log({
- type: `topic-move`,
- uid: socket.uid,
- ip: socket.ip,
- tid: tid,
- fromCid: topicData.cid,
- toCid: data.cid,
- });
+ await api.topics.move(socket, {
+ tid: data.tids,
+ cid: data.cid,
});
-
- await categories.onTopicsMoved(cids);
};
diff --git a/src/user/admin.js b/src/user/admin.js
index 369aafee50..35598bbbd9 100644
--- a/src/user/admin.js
+++ b/src/user/admin.js
@@ -64,7 +64,7 @@ module.exports = function (User) {
'w'
);
fs.promises.appendFile(fd, `${fields.map(f => `"${f}"`).join(',')}\n`);
- await batch.processSortedSet('group:administrators:members', async (uids) => {
+ await batch.processSortedSet('users:joindate', async (uids) => {
const userFieldsToLoad = fields.filter(field => field !== 'ip' && field !== 'password');
const usersData = await User.getUsersFields(uids, userFieldsToLoad);
let userIps = [];
diff --git a/src/views/admin/advanced/hooks.tpl b/src/views/admin/advanced/hooks.tpl
index 44a417917b..61aaa26ebc 100644
--- a/src/views/admin/advanced/hooks.tpl
+++ b/src/views/admin/advanced/hooks.tpl
@@ -11,7 +11,7 @@