From 00a06e9253b9019e8b8e6b01a3fd87fed05a234d Mon Sep 17 00:00:00 2001 From: phit Date: Sun, 4 Sep 2016 20:02:02 +0200 Subject: [PATCH 01/28] Use mousetrap.js minified version didn't see a reason it shouldn't use the minified version --- src/meta/js.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/meta/js.js b/src/meta/js.js index 3d85c7efb1..79377826ba 100644 --- a/src/meta/js.js +++ b/src/meta/js.js @@ -79,7 +79,7 @@ module.exports = function(Meta) { // modules listed below are routed through express (/src/modules) so they can be defined anonymously modules: { "Chart.js": './node_modules/chart.js/dist/Chart.min.js', - "mousetrap.js": './node_modules/mousetrap/mousetrap.js', + "mousetrap.js": './node_modules/mousetrap/mousetrap.min.js', "jqueryui.js": 'public/vendor/jquery/js/jquery-ui.js', "buzz.js": 'public/vendor/buzz/buzz.js' } From a578840904c242330e876bb301da6077b8332af0 Mon Sep 17 00:00:00 2001 From: NodeBB Misty Date: Tue, 6 Sep 2016 09:02:34 -0400 Subject: [PATCH 02/28] Latest translations and fallbacks --- public/language/ar/user.json | 4 +++- public/language/bg/user.json | 4 +++- public/language/bn/user.json | 4 +++- public/language/cs/user.json | 4 +++- public/language/da/user.json | 4 +++- public/language/de/user.json | 4 +++- public/language/el/user.json | 4 +++- public/language/en@pirate/user.json | 4 +++- public/language/en_US/user.json | 4 +++- public/language/es/error.json | 4 ++-- public/language/es/user.json | 12 +++++++----- public/language/et/user.json | 4 +++- public/language/fa_IR/user.json | 4 +++- public/language/fi/user.json | 4 +++- public/language/fr/user.json | 4 +++- public/language/gl/user.json | 4 +++- public/language/he/user.json | 4 +++- public/language/hu/user.json | 4 +++- public/language/id/user.json | 4 +++- public/language/it/user.json | 4 +++- public/language/ja/user.json | 4 +++- public/language/ko/user.json | 4 +++- public/language/lt/user.json | 4 +++- public/language/ms/user.json | 4 +++- public/language/nb/user.json | 4 +++- public/language/nl/user.json | 4 +++- public/language/pl/user.json | 4 +++- public/language/pt_BR/user.json | 4 +++- public/language/ro/user.json | 4 +++- public/language/ru/user.json | 4 +++- public/language/rw/user.json | 4 +++- public/language/sc/user.json | 4 +++- public/language/sk/user.json | 4 +++- public/language/sl/user.json | 4 +++- public/language/sr/user.json | 4 +++- public/language/sv/user.json | 4 +++- public/language/th/user.json | 4 +++- public/language/tr/error.json | 2 +- public/language/tr/user.json | 4 +++- public/language/vi/user.json | 4 +++- public/language/zh_CN/user.json | 4 +++- public/language/zh_TW/user.json | 4 +++- 42 files changed, 127 insertions(+), 47 deletions(-) diff --git a/public/language/ar/user.json b/public/language/ar/user.json index 65b93f7a46..0cabd41e33 100644 --- a/public/language/ar/user.json +++ b/public/language/ar/user.json @@ -118,5 +118,7 @@ "info.ban-history": "Recent Ban History", "info.no-ban-history": "This user has never been banned", "info.banned-until": "Banned until %1", - "info.banned-permanently": "Banned permanently" + "info.banned-permanently": "Banned permanently", + "info.banned-reason-label": "Reason", + "info.banned-no-reason": "No reason given." } \ No newline at end of file diff --git a/public/language/bg/user.json b/public/language/bg/user.json index 624c56c91f..29b6a15bb5 100644 --- a/public/language/bg/user.json +++ b/public/language/bg/user.json @@ -118,5 +118,7 @@ "info.ban-history": "Скорошна история на блокиранията", "info.no-ban-history": "Този потребител никога не е бил блокиран", "info.banned-until": "Блокиран до %1", - "info.banned-permanently": "Блокиран за постоянно" + "info.banned-permanently": "Блокиран за постоянно", + "info.banned-reason-label": "Reason", + "info.banned-no-reason": "No reason given." } \ No newline at end of file diff --git a/public/language/bn/user.json b/public/language/bn/user.json index 94430186a6..aab8625def 100644 --- a/public/language/bn/user.json +++ b/public/language/bn/user.json @@ -118,5 +118,7 @@ "info.ban-history": "Recent Ban History", "info.no-ban-history": "This user has never been banned", "info.banned-until": "Banned until %1", - "info.banned-permanently": "Banned permanently" + "info.banned-permanently": "Banned permanently", + "info.banned-reason-label": "Reason", + "info.banned-no-reason": "No reason given." } \ No newline at end of file diff --git a/public/language/cs/user.json b/public/language/cs/user.json index f4c5c62fd8..74fa70dbac 100644 --- a/public/language/cs/user.json +++ b/public/language/cs/user.json @@ -118,5 +118,7 @@ "info.ban-history": "Recent Ban History", "info.no-ban-history": "This user has never been banned", "info.banned-until": "Banned until %1", - "info.banned-permanently": "Banned permanently" + "info.banned-permanently": "Banned permanently", + "info.banned-reason-label": "Reason", + "info.banned-no-reason": "No reason given." } \ No newline at end of file diff --git a/public/language/da/user.json b/public/language/da/user.json index 3391eb928c..95fb3bb216 100644 --- a/public/language/da/user.json +++ b/public/language/da/user.json @@ -118,5 +118,7 @@ "info.ban-history": "Recent Ban History", "info.no-ban-history": "This user has never been banned", "info.banned-until": "Banned until %1", - "info.banned-permanently": "Banned permanently" + "info.banned-permanently": "Banned permanently", + "info.banned-reason-label": "Reason", + "info.banned-no-reason": "No reason given." } \ No newline at end of file diff --git a/public/language/de/user.json b/public/language/de/user.json index fe75e315ae..aa68a203ba 100644 --- a/public/language/de/user.json +++ b/public/language/de/user.json @@ -118,5 +118,7 @@ "info.ban-history": "Recent Ban History", "info.no-ban-history": "This user has never been banned", "info.banned-until": "Banned until %1", - "info.banned-permanently": "Banned permanently" + "info.banned-permanently": "Banned permanently", + "info.banned-reason-label": "Reason", + "info.banned-no-reason": "No reason given." } \ No newline at end of file diff --git a/public/language/el/user.json b/public/language/el/user.json index 5bb86d6774..44b7a6cde2 100644 --- a/public/language/el/user.json +++ b/public/language/el/user.json @@ -118,5 +118,7 @@ "info.ban-history": "Recent Ban History", "info.no-ban-history": "This user has never been banned", "info.banned-until": "Banned until %1", - "info.banned-permanently": "Banned permanently" + "info.banned-permanently": "Banned permanently", + "info.banned-reason-label": "Reason", + "info.banned-no-reason": "No reason given." } \ No newline at end of file diff --git a/public/language/en@pirate/user.json b/public/language/en@pirate/user.json index 52b1802eeb..c9236ae85b 100644 --- a/public/language/en@pirate/user.json +++ b/public/language/en@pirate/user.json @@ -118,5 +118,7 @@ "info.ban-history": "Recent Ban History", "info.no-ban-history": "This user has never been banned", "info.banned-until": "Banned until %1", - "info.banned-permanently": "Banned permanently" + "info.banned-permanently": "Banned permanently", + "info.banned-reason-label": "Reason", + "info.banned-no-reason": "No reason given." } \ No newline at end of file diff --git a/public/language/en_US/user.json b/public/language/en_US/user.json index d188d01f61..bbe1545754 100644 --- a/public/language/en_US/user.json +++ b/public/language/en_US/user.json @@ -118,5 +118,7 @@ "info.ban-history": "Recent Ban History", "info.no-ban-history": "This user has never been banned", "info.banned-until": "Banned until %1", - "info.banned-permanently": "Banned permanently" + "info.banned-permanently": "Banned permanently", + "info.banned-reason-label": "Reason", + "info.banned-no-reason": "No reason given." } \ No newline at end of file diff --git a/public/language/es/error.json b/public/language/es/error.json index 15cf9dcfec..1c4a62a244 100644 --- a/public/language/es/error.json +++ b/public/language/es/error.json @@ -124,6 +124,6 @@ "cant-kick-self": "No te puedes expulsar a ti mismo del grupo", "no-users-selected": "Ningun usuario(s) seleccionado", "invalid-home-page-route": "Ruta de página de inicio invalida", - "invalid-session": "Session Mismatch", - "invalid-session-text": "It looks like your login session is no longer active, or no longer matches with the server. Please refresh this page." + "invalid-session": "No concuerdan los datos de sesión", + "invalid-session-text": "Parece que su sesión ha expirado o no concuerda con el servidor. Por favor vuelva a cargar la página." } \ No newline at end of file diff --git a/public/language/es/user.json b/public/language/es/user.json index da036d0b7e..c9c92b53eb 100644 --- a/public/language/es/user.json +++ b/public/language/es/user.json @@ -89,10 +89,10 @@ "topics_per_page": "Temas por página", "posts_per_page": "Post por página", "notification_sounds": "Reproducir un sonido al recibir una notificación", - "notifications_and_sounds": "Notifications & Sounds", - "incoming-message-sound": "Incoming message sound", - "outgoing-message-sound": "Outgoing message sound", - "notification-sound": "Notification sound", + "notifications_and_sounds": "Notificaciones y Sonidos", + "incoming-message-sound": "Sonido del mensaje entrante", + "outgoing-message-sound": "Sonido del mensaje saliente", + "notification-sound": "Sonido de notificación", "browsing": "Preferencias de navegación.", "open_links_in_new_tab": "Abrir los enlaces externos en una nueva pestaña", "enable_topic_searching": "Activar la búsqueda \"dentro del tema\"", @@ -118,5 +118,7 @@ "info.ban-history": "Histórico reciente de bans", "info.no-ban-history": "Este usuario nunca ha sido baneado", "info.banned-until": "Baneado hasta %1", - "info.banned-permanently": "Baneado permanentemente" + "info.banned-permanently": "Baneado permanentemente", + "info.banned-reason-label": "Motivo", + "info.banned-no-reason": "Motivo no especificado" } \ No newline at end of file diff --git a/public/language/et/user.json b/public/language/et/user.json index 1d63381814..2a7187b440 100644 --- a/public/language/et/user.json +++ b/public/language/et/user.json @@ -118,5 +118,7 @@ "info.ban-history": "Hiljutiste keeldude ajalugu", "info.no-ban-history": "Seda kasutajat pole kunagi keelustatud", "info.banned-until": "Keelustatud kuni %1", - "info.banned-permanently": "Igavesti keelustatud" + "info.banned-permanently": "Igavesti keelustatud", + "info.banned-reason-label": "Reason", + "info.banned-no-reason": "No reason given." } \ No newline at end of file diff --git a/public/language/fa_IR/user.json b/public/language/fa_IR/user.json index bb40b5dd73..639bed11f4 100644 --- a/public/language/fa_IR/user.json +++ b/public/language/fa_IR/user.json @@ -118,5 +118,7 @@ "info.ban-history": "تاریخچه مسدودیت اخیر", "info.no-ban-history": "این کاربر هرگز مسدود نشده است", "info.banned-until": "مسدود شده تا %1", - "info.banned-permanently": "مسدود شده به طور دائم" + "info.banned-permanently": "مسدود شده به طور دائم", + "info.banned-reason-label": "دلیل", + "info.banned-no-reason": "هیچ دلیلی ارایه نشد." } \ No newline at end of file diff --git a/public/language/fi/user.json b/public/language/fi/user.json index 66ca174258..2620d68586 100644 --- a/public/language/fi/user.json +++ b/public/language/fi/user.json @@ -118,5 +118,7 @@ "info.ban-history": "Recent Ban History", "info.no-ban-history": "This user has never been banned", "info.banned-until": "Banned until %1", - "info.banned-permanently": "Banned permanently" + "info.banned-permanently": "Banned permanently", + "info.banned-reason-label": "Reason", + "info.banned-no-reason": "No reason given." } \ No newline at end of file diff --git a/public/language/fr/user.json b/public/language/fr/user.json index 076e154971..05683ca3ad 100644 --- a/public/language/fr/user.json +++ b/public/language/fr/user.json @@ -118,5 +118,7 @@ "info.ban-history": "Historique de bannissement récent", "info.no-ban-history": "Cet utilisateur n'a jamais été banni", "info.banned-until": "Banni jusqu'au %1", - "info.banned-permanently": "Banni de façon permanente" + "info.banned-permanently": "Banni de façon permanente", + "info.banned-reason-label": "Raison", + "info.banned-no-reason": "Aucune raison donnée" } \ No newline at end of file diff --git a/public/language/gl/user.json b/public/language/gl/user.json index 9cdea0f306..b19642dcee 100644 --- a/public/language/gl/user.json +++ b/public/language/gl/user.json @@ -118,5 +118,7 @@ "info.ban-history": "Histórico recente de bans", "info.no-ban-history": "Este usuario nunca foi baneado", "info.banned-until": "Baneado hasta 1%", - "info.banned-permanently": "Baneado permanentemente" + "info.banned-permanently": "Baneado permanentemente", + "info.banned-reason-label": "Reason", + "info.banned-no-reason": "No reason given." } \ No newline at end of file diff --git a/public/language/he/user.json b/public/language/he/user.json index 7287d5c1f0..06425a7113 100644 --- a/public/language/he/user.json +++ b/public/language/he/user.json @@ -118,5 +118,7 @@ "info.ban-history": "Recent Ban History", "info.no-ban-history": "This user has never been banned", "info.banned-until": "Banned until %1", - "info.banned-permanently": "Banned permanently" + "info.banned-permanently": "Banned permanently", + "info.banned-reason-label": "Reason", + "info.banned-no-reason": "No reason given." } \ No newline at end of file diff --git a/public/language/hu/user.json b/public/language/hu/user.json index 19bd8c3154..d69f15ed01 100644 --- a/public/language/hu/user.json +++ b/public/language/hu/user.json @@ -118,5 +118,7 @@ "info.ban-history": "Recent Ban History", "info.no-ban-history": "This user has never been banned", "info.banned-until": "Banned until %1", - "info.banned-permanently": "Banned permanently" + "info.banned-permanently": "Banned permanently", + "info.banned-reason-label": "Reason", + "info.banned-no-reason": "No reason given." } \ No newline at end of file diff --git a/public/language/id/user.json b/public/language/id/user.json index 715648a4bd..81aad04dbf 100644 --- a/public/language/id/user.json +++ b/public/language/id/user.json @@ -118,5 +118,7 @@ "info.ban-history": "Recent Ban History", "info.no-ban-history": "This user has never been banned", "info.banned-until": "Banned until %1", - "info.banned-permanently": "Banned permanently" + "info.banned-permanently": "Banned permanently", + "info.banned-reason-label": "Reason", + "info.banned-no-reason": "No reason given." } \ No newline at end of file diff --git a/public/language/it/user.json b/public/language/it/user.json index d1f1edafa7..767099e268 100644 --- a/public/language/it/user.json +++ b/public/language/it/user.json @@ -118,5 +118,7 @@ "info.ban-history": "Storico dei Ban recenti", "info.no-ban-history": "Questo utente non è mai stato bannato", "info.banned-until": "Bannato fino %1", - "info.banned-permanently": "Bannato permanentemente" + "info.banned-permanently": "Bannato permanentemente", + "info.banned-reason-label": "Motivo", + "info.banned-no-reason": "Non è stata data nessuna motivazione." } \ No newline at end of file diff --git a/public/language/ja/user.json b/public/language/ja/user.json index 16cd6a8811..acf7ffdac4 100644 --- a/public/language/ja/user.json +++ b/public/language/ja/user.json @@ -118,5 +118,7 @@ "info.ban-history": "Recent Ban History", "info.no-ban-history": "This user has never been banned", "info.banned-until": "Banned until %1", - "info.banned-permanently": "Banned permanently" + "info.banned-permanently": "Banned permanently", + "info.banned-reason-label": "Reason", + "info.banned-no-reason": "No reason given." } \ No newline at end of file diff --git a/public/language/ko/user.json b/public/language/ko/user.json index bd8404161d..3c12333aa5 100644 --- a/public/language/ko/user.json +++ b/public/language/ko/user.json @@ -118,5 +118,7 @@ "info.ban-history": "Recent Ban History", "info.no-ban-history": "This user has never been banned", "info.banned-until": "Banned until %1", - "info.banned-permanently": "Banned permanently" + "info.banned-permanently": "Banned permanently", + "info.banned-reason-label": "Reason", + "info.banned-no-reason": "No reason given." } \ No newline at end of file diff --git a/public/language/lt/user.json b/public/language/lt/user.json index 31f55b8307..3c9e71ba28 100644 --- a/public/language/lt/user.json +++ b/public/language/lt/user.json @@ -118,5 +118,7 @@ "info.ban-history": "Recent Ban History", "info.no-ban-history": "This user has never been banned", "info.banned-until": "Banned until %1", - "info.banned-permanently": "Banned permanently" + "info.banned-permanently": "Banned permanently", + "info.banned-reason-label": "Reason", + "info.banned-no-reason": "No reason given." } \ No newline at end of file diff --git a/public/language/ms/user.json b/public/language/ms/user.json index 4e89cfb478..954536c92f 100644 --- a/public/language/ms/user.json +++ b/public/language/ms/user.json @@ -118,5 +118,7 @@ "info.ban-history": "Recent Ban History", "info.no-ban-history": "This user has never been banned", "info.banned-until": "Banned until %1", - "info.banned-permanently": "Banned permanently" + "info.banned-permanently": "Banned permanently", + "info.banned-reason-label": "Reason", + "info.banned-no-reason": "No reason given." } \ No newline at end of file diff --git a/public/language/nb/user.json b/public/language/nb/user.json index 056afeb82f..1967d82e28 100644 --- a/public/language/nb/user.json +++ b/public/language/nb/user.json @@ -118,5 +118,7 @@ "info.ban-history": "Recent Ban History", "info.no-ban-history": "This user has never been banned", "info.banned-until": "Banned until %1", - "info.banned-permanently": "Banned permanently" + "info.banned-permanently": "Banned permanently", + "info.banned-reason-label": "Reason", + "info.banned-no-reason": "No reason given." } \ No newline at end of file diff --git a/public/language/nl/user.json b/public/language/nl/user.json index 7d56f88b4a..ef08a49eed 100644 --- a/public/language/nl/user.json +++ b/public/language/nl/user.json @@ -118,5 +118,7 @@ "info.ban-history": "Recente verban-geschiedenis", "info.no-ban-history": "Deze gebruiker is nooit eerder verbannen", "info.banned-until": "Verbannen tot %1", - "info.banned-permanently": "Voor altijd verbannen" + "info.banned-permanently": "Voor altijd verbannen", + "info.banned-reason-label": "Reason", + "info.banned-no-reason": "No reason given." } \ No newline at end of file diff --git a/public/language/pl/user.json b/public/language/pl/user.json index 0a359430c6..d18d541262 100644 --- a/public/language/pl/user.json +++ b/public/language/pl/user.json @@ -118,5 +118,7 @@ "info.ban-history": "Historia ostatnich banów", "info.no-ban-history": "Ten użytkownik nigdy nie był zbanowany", "info.banned-until": "Zbanowany do %1", - "info.banned-permanently": "Zbanowany permanentnie" + "info.banned-permanently": "Zbanowany permanentnie", + "info.banned-reason-label": "Reason", + "info.banned-no-reason": "No reason given." } \ No newline at end of file diff --git a/public/language/pt_BR/user.json b/public/language/pt_BR/user.json index 1deb677bd6..1aa9655aea 100644 --- a/public/language/pt_BR/user.json +++ b/public/language/pt_BR/user.json @@ -118,5 +118,7 @@ "info.ban-history": "Histórico de Banimentos Recentes", "info.no-ban-history": "Este usuário nunca foi banido", "info.banned-until": "Banido até %1", - "info.banned-permanently": "Banido permanentemente" + "info.banned-permanently": "Banido permanentemente", + "info.banned-reason-label": "Reason", + "info.banned-no-reason": "No reason given." } \ No newline at end of file diff --git a/public/language/ro/user.json b/public/language/ro/user.json index 31cd396f7f..c2d7d38fa5 100644 --- a/public/language/ro/user.json +++ b/public/language/ro/user.json @@ -118,5 +118,7 @@ "info.ban-history": "Recent Ban History", "info.no-ban-history": "This user has never been banned", "info.banned-until": "Banned until %1", - "info.banned-permanently": "Banned permanently" + "info.banned-permanently": "Banned permanently", + "info.banned-reason-label": "Reason", + "info.banned-no-reason": "No reason given." } \ No newline at end of file diff --git a/public/language/ru/user.json b/public/language/ru/user.json index 33c31e3b43..56516c66cf 100644 --- a/public/language/ru/user.json +++ b/public/language/ru/user.json @@ -118,5 +118,7 @@ "info.ban-history": "Recent Ban History", "info.no-ban-history": "This user has never been banned", "info.banned-until": "Banned until %1", - "info.banned-permanently": "Banned permanently" + "info.banned-permanently": "Banned permanently", + "info.banned-reason-label": "Reason", + "info.banned-no-reason": "No reason given." } \ No newline at end of file diff --git a/public/language/rw/user.json b/public/language/rw/user.json index a598704593..ced7a21ee2 100644 --- a/public/language/rw/user.json +++ b/public/language/rw/user.json @@ -118,5 +118,7 @@ "info.ban-history": "Recent Ban History", "info.no-ban-history": "This user has never been banned", "info.banned-until": "Banned until %1", - "info.banned-permanently": "Banned permanently" + "info.banned-permanently": "Banned permanently", + "info.banned-reason-label": "Reason", + "info.banned-no-reason": "No reason given." } \ No newline at end of file diff --git a/public/language/sc/user.json b/public/language/sc/user.json index acdd61fe69..11b7d69f80 100644 --- a/public/language/sc/user.json +++ b/public/language/sc/user.json @@ -118,5 +118,7 @@ "info.ban-history": "Recent Ban History", "info.no-ban-history": "This user has never been banned", "info.banned-until": "Banned until %1", - "info.banned-permanently": "Banned permanently" + "info.banned-permanently": "Banned permanently", + "info.banned-reason-label": "Reason", + "info.banned-no-reason": "No reason given." } \ No newline at end of file diff --git a/public/language/sk/user.json b/public/language/sk/user.json index 017be26335..aab0649093 100644 --- a/public/language/sk/user.json +++ b/public/language/sk/user.json @@ -118,5 +118,7 @@ "info.ban-history": "Recent Ban History", "info.no-ban-history": "This user has never been banned", "info.banned-until": "Banned until %1", - "info.banned-permanently": "Banned permanently" + "info.banned-permanently": "Banned permanently", + "info.banned-reason-label": "Reason", + "info.banned-no-reason": "No reason given." } \ No newline at end of file diff --git a/public/language/sl/user.json b/public/language/sl/user.json index 9a778e442c..02d9435768 100644 --- a/public/language/sl/user.json +++ b/public/language/sl/user.json @@ -118,5 +118,7 @@ "info.ban-history": "Recent Ban History", "info.no-ban-history": "This user has never been banned", "info.banned-until": "Banned until %1", - "info.banned-permanently": "Banned permanently" + "info.banned-permanently": "Banned permanently", + "info.banned-reason-label": "Reason", + "info.banned-no-reason": "No reason given." } \ No newline at end of file diff --git a/public/language/sr/user.json b/public/language/sr/user.json index 1016a9feb4..ac03bc3216 100644 --- a/public/language/sr/user.json +++ b/public/language/sr/user.json @@ -118,5 +118,7 @@ "info.ban-history": "Recent Ban History", "info.no-ban-history": "This user has never been banned", "info.banned-until": "Banned until %1", - "info.banned-permanently": "Banned permanently" + "info.banned-permanently": "Banned permanently", + "info.banned-reason-label": "Reason", + "info.banned-no-reason": "No reason given." } \ No newline at end of file diff --git a/public/language/sv/user.json b/public/language/sv/user.json index ba150f3294..5dcdf8a80a 100644 --- a/public/language/sv/user.json +++ b/public/language/sv/user.json @@ -118,5 +118,7 @@ "info.ban-history": "Recent Ban History", "info.no-ban-history": "This user has never been banned", "info.banned-until": "Banned until %1", - "info.banned-permanently": "Banned permanently" + "info.banned-permanently": "Banned permanently", + "info.banned-reason-label": "Reason", + "info.banned-no-reason": "No reason given." } \ No newline at end of file diff --git a/public/language/th/user.json b/public/language/th/user.json index a59c28d7b5..d489070ae9 100644 --- a/public/language/th/user.json +++ b/public/language/th/user.json @@ -118,5 +118,7 @@ "info.ban-history": "Recent Ban History", "info.no-ban-history": "This user has never been banned", "info.banned-until": "Banned until %1", - "info.banned-permanently": "Banned permanently" + "info.banned-permanently": "Banned permanently", + "info.banned-reason-label": "Reason", + "info.banned-no-reason": "No reason given." } \ No newline at end of file diff --git a/public/language/tr/error.json b/public/language/tr/error.json index b3f6216519..f90a44f198 100644 --- a/public/language/tr/error.json +++ b/public/language/tr/error.json @@ -124,6 +124,6 @@ "cant-kick-self": "Kendinizi gruptan atamazsınız.", "no-users-selected": "Seçili kullanıcı(s) bulunamadı", "invalid-home-page-route": "Geçersiz anasayfa yolu", - "invalid-session": "Session Mismatch", + "invalid-session": "Oturum Uyuşmazlığı", "invalid-session-text": "It looks like your login session is no longer active, or no longer matches with the server. Please refresh this page." } \ No newline at end of file diff --git a/public/language/tr/user.json b/public/language/tr/user.json index 22d9fdaa00..338b0dddf7 100644 --- a/public/language/tr/user.json +++ b/public/language/tr/user.json @@ -118,5 +118,7 @@ "info.ban-history": "Yasaklama Olayları", "info.no-ban-history": "Bu kullanıcı hiç yasaklanmadı", "info.banned-until": "Yasaklama süresi %1", - "info.banned-permanently": "Kalıcı yasakla" + "info.banned-permanently": "Kalıcı yasakla", + "info.banned-reason-label": "Gerekçe", + "info.banned-no-reason": "Gerekçe belirtilmedi." } \ No newline at end of file diff --git a/public/language/vi/user.json b/public/language/vi/user.json index 3c746b87ca..8a00a21653 100644 --- a/public/language/vi/user.json +++ b/public/language/vi/user.json @@ -118,5 +118,7 @@ "info.ban-history": "Recent Ban History", "info.no-ban-history": "This user has never been banned", "info.banned-until": "Banned until %1", - "info.banned-permanently": "Banned permanently" + "info.banned-permanently": "Banned permanently", + "info.banned-reason-label": "Reason", + "info.banned-no-reason": "No reason given." } \ No newline at end of file diff --git a/public/language/zh_CN/user.json b/public/language/zh_CN/user.json index 766b80c570..c5fee0f7e2 100644 --- a/public/language/zh_CN/user.json +++ b/public/language/zh_CN/user.json @@ -118,5 +118,7 @@ "info.ban-history": "最近封禁历史", "info.no-ban-history": "该用户从未被封禁", "info.banned-until": "封禁到 %1", - "info.banned-permanently": "永久封禁" + "info.banned-permanently": "永久封禁", + "info.banned-reason-label": "Reason", + "info.banned-no-reason": "No reason given." } \ No newline at end of file diff --git a/public/language/zh_TW/user.json b/public/language/zh_TW/user.json index 4832a248a5..f23c2414a5 100644 --- a/public/language/zh_TW/user.json +++ b/public/language/zh_TW/user.json @@ -118,5 +118,7 @@ "info.ban-history": "最近禁用歷史", "info.no-ban-history": "這個使用者永遠不會被禁用", "info.banned-until": "禁用至 %1", - "info.banned-permanently": "永久禁用" + "info.banned-permanently": "永久禁用", + "info.banned-reason-label": "Reason", + "info.banned-no-reason": "No reason given." } \ No newline at end of file From 3cdfd22a65ef479241bdadc5b6be43ca0bc3f80f Mon Sep 17 00:00:00 2001 From: barisusakli Date: Tue, 6 Sep 2016 20:20:07 +0300 Subject: [PATCH 03/28] on user delete remove their uid from digests --- src/user/delete.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/user/delete.js b/src/user/delete.js index 6c5a92e225..66d351cdf2 100644 --- a/src/user/delete.js +++ b/src/user/delete.js @@ -94,7 +94,10 @@ module.exports = function(User) { 'users:reputation', 'users:banned', 'users:online', - 'users:notvalidated' + 'users:notvalidated', + 'digest:day:uids', + 'digest:week:uids', + 'digest:month:uids' ], uid, next); }, function(next) { From 1b7aa5ac12d058d52add39205227b21700fdeca8 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Wed, 7 Sep 2016 00:15:29 +0300 Subject: [PATCH 04/28] up vanilla --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index ce5cc7ac19..70bf0494e4 100644 --- a/package.json +++ b/package.json @@ -61,7 +61,7 @@ "nodebb-rewards-essentials": "0.0.9", "nodebb-theme-lavender": "3.0.14", "nodebb-theme-persona": "4.1.42", - "nodebb-theme-vanilla": "5.1.25", + "nodebb-theme-vanilla": "5.1.26", "nodebb-widget-essentials": "2.0.10", "nodemailer": "2.0.0", "nodemailer-sendmail-transport": "1.0.0", From 3cf44490205d3f9822eb1859be5b42a72379c885 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Wed, 7 Sep 2016 02:08:54 +0300 Subject: [PATCH 05/28] use eachSeries --- src/install.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/install.js b/src/install.js index 296e30fdc4..8cf99ba264 100644 --- a/src/install.js +++ b/src/install.js @@ -182,7 +182,7 @@ function setupDefaultConfigs(next) { var meta = require('./meta'), defaults = require(path.join(__dirname, '../', 'install/data/defaults.json')); - async.each(Object.keys(defaults), function (key, next) { + async.eachSeries(Object.keys(defaults), function (key, next) { meta.configs.setOnEmpty(key, defaults[key], next); }, function (err) { if (err) { From 24e0c9f6fee929bcf4f2cdb71f47b185c23a6d3b Mon Sep 17 00:00:00 2001 From: NodeBB Misty Date: Wed, 7 Sep 2016 09:02:30 -0400 Subject: [PATCH 06/28] Latest translations and fallbacks --- public/language/bg/user.json | 4 +- public/language/es/error.json | 14 +++--- public/language/lt/category.json | 16 +++---- public/language/lt/email.json | 6 +-- public/language/lt/uploads.json | 8 ++-- public/language/lt/user.json | 72 ++++++++++++++--------------- public/language/lt/users.json | 2 +- public/language/zh_TW/error.json | 34 +++++++------- public/language/zh_TW/groups.json | 4 +- public/language/zh_TW/modules.json | 4 +- public/language/zh_TW/pages.json | 10 ++-- public/language/zh_TW/register.json | 8 ++-- public/language/zh_TW/topic.json | 6 +-- public/language/zh_TW/user.json | 16 +++---- public/language/zh_TW/users.json | 6 +-- 15 files changed, 105 insertions(+), 105 deletions(-) diff --git a/public/language/bg/user.json b/public/language/bg/user.json index 29b6a15bb5..d02f6fcfc1 100644 --- a/public/language/bg/user.json +++ b/public/language/bg/user.json @@ -119,6 +119,6 @@ "info.no-ban-history": "Този потребител никога не е бил блокиран", "info.banned-until": "Блокиран до %1", "info.banned-permanently": "Блокиран за постоянно", - "info.banned-reason-label": "Reason", - "info.banned-no-reason": "No reason given." + "info.banned-reason-label": "Причина", + "info.banned-no-reason": "Няма посочена причина." } \ No newline at end of file diff --git a/public/language/es/error.json b/public/language/es/error.json index 1c4a62a244..288e4c4088 100644 --- a/public/language/es/error.json +++ b/public/language/es/error.json @@ -48,13 +48,13 @@ "post-edit-duration-expired-hours-minutes": "No puedes editar mensajes hasta pasado %1 hora(s) y %2 minuto(s) después de haberlo escrito", "post-edit-duration-expired-days": "No puedes editar mensajes hasta pasado %1 día(s) después de haberlo escrito", "post-edit-duration-expired-days-hours": "No puedes editar mensajes hasta pasado %1 día(s) y %2 hora(s) después de haberlo escrito", - "post-delete-duration-expired": "No puedes borrar mensajes hasta pasado %1 segundo(s) después de haberlo escrito", - "post-delete-duration-expired-minutes": "No puedes borrar mensajes hasta pasado %1 minuto(s) después de haberlo escrito", - "post-delete-duration-expired-minutes-seconds": "No puedes borrar mensajes hasta pasado %1 minuto(s) y %2 segundo(s) después de haberlo escrito", - "post-delete-duration-expired-hours": "No puedes borrar mensajes hasta pasado %1 hora(s) después de haberlo escrito", - "post-delete-duration-expired-hours-minutes": "No puedes borrar mensajes hasta pasado %1 hora(s) y %2 minuto(s) después de haberlo escrito", - "post-delete-duration-expired-days": "No puedes borrar mensajes hasta pasado %1 día(s) después de haberlo escrito", - "post-delete-duration-expired-days-hours": "No puedes borrar mensajes hasta pasado %1 día(s) y %2 hora(s) después de haberlo escrito", + "post-delete-duration-expired": "No puedes borrar mensajes tras pasar %1 segundo(s) después de haberlo escrito", + "post-delete-duration-expired-minutes": "No puedes borrar mensajes tras pasar %1 minuto(s) después de haberlo escrito", + "post-delete-duration-expired-minutes-seconds": "No puedes borrar mensajes tras pasar %1 minuto(s) y %2 segundo(s) después de haberlo escrito", + "post-delete-duration-expired-hours": "No puedes borrar mensajes tras pasar %1 hora(s) después de haberlo escrito", + "post-delete-duration-expired-hours-minutes": "No puedes borrar mensajes tras pasar %1 hora(s) y %2 minuto(s) después de haberlo escrito", + "post-delete-duration-expired-days": "No puedes borrar mensajes tras pasar %1 día(s) después de haberlo escrito", + "post-delete-duration-expired-days-hours": "No puedes borrar mensajes tras pasar %1 día(s) y %2 hora(s) después de haberlo escrito", "cant-delete-topic-has-reply": "No puedes borrar tu tema después de que tenga respuestas", "cant-delete-topic-has-replies": "No puedes borrar tu tema despues de que tenga ℅1 respuestas", "content-too-short": "Por favor introduzca una publicación más larga. Las publicaciones deben contener al menos %1 caractere(s).", diff --git a/public/language/lt/category.json b/public/language/lt/category.json index 8c909bae42..abe245da73 100644 --- a/public/language/lt/category.json +++ b/public/language/lt/category.json @@ -1,20 +1,20 @@ { - "category": "Category", - "subcategories": "Subcategories", + "category": "Kategorija", + "subcategories": "Subkategorijos", "new_topic_button": "Nauja tema", "guest-login-post": "Prisijungti įrašų paskelbimui", "no_topics": "Šioje kategorijoje temų nėra.
Kodėl gi jums nesukūrus naujos?", "browsing": "naršo", - "no_replies": "Niekas dar neatsakė", - "no_new_posts": "No new posts.", + "no_replies": "Nėra atsakymų", + "no_new_posts": "Nėra naujų pranešimų.", "share_this_category": "Pasidalinti šią kategoriją", "watch": "Stebėti", - "ignore": "Nepaisyti", - "watching": "Watching", - "ignoring": "Ignoring", + "ignore": "Ignoruoti", + "watching": "Stebima", + "ignoring": "Ignoruojama", "watching.description": "Show topics in unread", "ignoring.description": "Do not show topics in unread", "watch.message": "Jūs dabar stebite atnaujinimus iš šios kategorijos", "ignore.message": "Jūs dabar ignoruojate atnaujinimus iš šios kategorijos", - "watched-categories": "Watched categories" + "watched-categories": "Stebimos kategorijos" } \ No newline at end of file diff --git a/public/language/lt/email.json b/public/language/lt/email.json index f183b346f2..d4f24c89e5 100644 --- a/public/language/lt/email.json +++ b/public/language/lt/email.json @@ -21,9 +21,9 @@ "digest.cta": "Kad aplankyti %1, spauskite čia", "digest.unsub.info": "Ši santrauka buvo išsiųsta į tavo prenumeratos nustatymus", "digest.no_topics": "Nebuvo aktyvių temų praeityje %1", - "digest.day": "day", - "digest.week": "week", - "digest.month": "month", + "digest.day": "diena", + "digest.week": "savaitė", + "digest.month": "mėnuo", "digest.subject": "Digest for %1", "notif.chat.subject": "Nauja pokalbio žinutė gauta iš %1", "notif.chat.cta": "Pokalbio pratęsimui spauskite čia", diff --git a/public/language/lt/uploads.json b/public/language/lt/uploads.json index 1622cb5693..a838cdf68d 100644 --- a/public/language/lt/uploads.json +++ b/public/language/lt/uploads.json @@ -1,6 +1,6 @@ { - "uploading-file": "Uploading the file...", - "select-file-to-upload": "Select a file to upload!", - "upload-success": "File uploaded successfully!", - "maximum-file-size": "Maximum %1 kb" + "uploading-file": "įkeliama...", + "select-file-to-upload": "Pasirinkite failą, kurį norite įkelti.", + "upload-success": "Failas įkeltas sėkmingai!", + "maximum-file-size": "Daugiausiai %1 kb" } \ No newline at end of file diff --git a/public/language/lt/user.json b/public/language/lt/user.json index 3c9e71ba28..da60c551d8 100644 --- a/public/language/lt/user.json +++ b/public/language/lt/user.json @@ -6,14 +6,14 @@ "postcount": "Įrašų kiekis", "email": "El. paštas", "confirm_email": "Patvirtinti el. paštą", - "account_info": "Account Info", + "account_info": "Paskyros informacija", "ban_account": "Užblokuoti Paskyrą", "ban_account_confirm": "Jūs tikrai norite užblokuoti šį vartotoją?", "unban_account": "Atblokuoti Paskyrą", "delete_account": "Ištrinti paskyrą", "delete_account_confirm": "Ar tikrai norite ištrinti savo paskyrą?
Šis veiksmas yra negrįžtamas, ir jūs negalėsite susigrąžinti jokių duomenų

Įveskite savo vardą, kad patvirtintumėte, jog norite panaikinti šią paskyrą.", "delete_this_account_confirm": "Ar jūs tikrai norite ištrint šią paskyrą?
Šis veiksmas nebesugražinamas ir jūs nebegalėsite atgauti jokių duomenų

", - "account-deleted": "Account deleted", + "account-deleted": "Paskyra ištrinta", "fullname": "Vardas ir pavardė", "website": "Tinklalapis", "location": "Vieta", @@ -23,7 +23,7 @@ "profile": "Profilis", "profile_views": "Profilio peržiūros", "reputation": "Reputacija", - "favourites": "Bookmarks", + "favourites": "Žymės", "watched": "Peržiūrėjo", "followers": "Sekėjai", "following": "Seka", @@ -31,17 +31,17 @@ "signature": "Parašas", "birthday": "Gimimo diena", "chat": "Susirašinėti", - "chat_with": "Chat with %1", + "chat_with": "Susirašinėti su %1", "follow": "Sekti", "unfollow": "Nesekti", "more": "Daugiau", "profile_update_success": "Profilis sėkmingai atnaujintas!", "change_picture": "Pakeisti paveikslėlį", - "change_username": "Change Username", - "change_email": "Change Email", + "change_username": "Keisti vartotojo vardą", + "change_email": "Keisti el. pašto adresą", "edit": "Redaguoti", - "edit-profile": "Edit Profile", - "default_picture": "Default Icon", + "edit-profile": "Redaguoti profilį", + "default_picture": "Standartinis paveikslėlis", "uploaded_picture": "Įkeltas paveikslėlis", "upload_new_picture": "Įkelti naują paveikslėlį", "upload_new_picture_from_url": "Įkelti naują paveikslėlį iš URL", @@ -56,12 +56,12 @@ "confirm_password": "Patvirtinkite slaptažodį", "password": "Slaptažodis", "username_taken_workaround": "Jūsų norimas vartotojo vardas jau užimtas, todėl mes jį šiek tiek pakeitėme. Dabar jūs esate žinomas kaip %1", - "password_same_as_username": "Your password is the same as your username, please select another password.", - "password_same_as_email": "Your password is the same as your email, please select another password.", + "password_same_as_username": "Jūsų slaptažodis sutampa su Jūsų vartotojo vardu. Dėl saugumo, prašome naudoti kitą slaptažodį.", + "password_same_as_email": "Jūsų slaptažodis sutampa su Jūsų el. pašto adresu. Dėl saugumo, prašome naudoti kitą slaptažodį.", "upload_picture": "Įkelti paveikslėlį", "upload_a_picture": "Įkelti paveikslėlį", - "remove_uploaded_picture": "Remove Uploaded Picture", - "upload_cover_picture": "Upload cover picture", + "remove_uploaded_picture": "Ištrinti paveikslėlį", + "upload_cover_picture": "Įkelti viršelio nuotrauką", "settings": "Nustatymai", "show_email": "Rodyti mano el. paštą viešai", "show_fullname": "Rodyti mano vardą ir pavardę", @@ -80,45 +80,45 @@ "has_no_posts": "Šis vartotojas pakolkas neparašė jokių pranešimų", "has_no_topics": "Šis vartotojas pakolkas nesukūrė jokių temų", "has_no_watched_topics": "Šis vartotojas pakolkas nestebėjo jokių temų", - "has_no_upvoted_posts": "This user hasn't upvoted any posts yet.", - "has_no_downvoted_posts": "This user hasn't downvoted any posts yet.", - "has_no_voted_posts": "This user has no voted posts", + "has_no_upvoted_posts": "Šis narys dar neturi teigiamai įvertintų pranešimų.", + "has_no_downvoted_posts": "Šis narys dar neturi neigiamai įvertintų pranešimų.", + "has_no_voted_posts": "Šis narys dar neturi įvertintų pranešimų.", "email_hidden": "El. paštas paslėptas", "hidden": "paslėptas", "paginate_description": "Puslapiavimas temų ir pranešimų, vietoj kad naudoti judėjimą su pelytė į viršų ir į apačia", "topics_per_page": "Temų puslapyje", "posts_per_page": "Pranešimų puslapyje", "notification_sounds": "Paleisti garsą kai jūs gaunate pranešimą", - "notifications_and_sounds": "Notifications & Sounds", - "incoming-message-sound": "Incoming message sound", - "outgoing-message-sound": "Outgoing message sound", - "notification-sound": "Notification sound", + "notifications_and_sounds": "Pranešimai ir garsai", + "incoming-message-sound": "Gaunamos žinutės garsas", + "outgoing-message-sound": "Siunčiamos žinutės garsas", + "notification-sound": "Pranešimo garsas", "browsing": "Naršymo nustatymai", "open_links_in_new_tab": "Atidaryti išeinančias nuorodas naujam skirtuke", "enable_topic_searching": "Įjungti Temų Ieškojimą ", "topic_search_help": "Jeigu įjungtas, temų ieškojimas, nepaisys naršyklės puslapio ieškojimo, ir pradės ieškoti tik toje temoje kuri bus rodoma ekrane", "delay_image_loading": "Delay Image Loading", - "image_load_delay_help": "If enabled, images in topics will not load until they are scrolled into view", - "scroll_to_my_post": "After posting a reply, show the new post", - "follow_topics_you_reply_to": "Watch topics that you reply to", - "follow_topics_you_create": "Watch topics you create", - "grouptitle": "Group Title", + "image_load_delay_help": "Jei įjungta, paveikslėliai temose nesikraus, kol nebus iki jų nuslinkta", + "scroll_to_my_post": "Po parašyto atsakymo, rodyti naują pranešimą", + "follow_topics_you_reply_to": "Peržiūrėti temas, kuriose Jūs atsakėte", + "follow_topics_you_create": "Peržiūrėti temas, kurias Jūs sukūrėte", + "grouptitle": "Grupės pavadinimas", "no-group-title": "Nėra grupės pavadinimo", - "select-skin": "Select a Skin", - "select-homepage": "Select a Homepage", - "homepage": "Homepage", - "homepage_description": "Select a page to use as the forum homepage or 'None' to use the default homepage.", - "custom_route": "Custom Homepage Route", + "select-skin": "Pasirinkite išvaizdą", + "select-homepage": "Pasirinkite pagrindinį puslapį", + "homepage": "Pagrindinis puslapis", + "homepage_description": "Pasirinkite puslapį kaip savo pagrindinį, arba pasirinkite \"Joks\" norėdami naudoti standartinį pagrindinį puslapį.", + "custom_route": "Pagrindinio puslapio vieta", "custom_route_help": "Enter a route name here, without any preceding slash (e.g. \"recent\", or \"popular\")", "sso.title": "Single Sign-on Services", "sso.associated": "Associated with", "sso.not-associated": "Click here to associate with", "info.latest-flags": "Latest Flags", - "info.no-flags": "No Flagged Posts Found", - "info.ban-history": "Recent Ban History", - "info.no-ban-history": "This user has never been banned", - "info.banned-until": "Banned until %1", - "info.banned-permanently": "Banned permanently", - "info.banned-reason-label": "Reason", - "info.banned-no-reason": "No reason given." + "info.no-flags": "Nerasta pažymėtų pranešimų", + "info.ban-history": "Blokavimų istorija", + "info.no-ban-history": "Šis narys nebuvo užblokuotas.", + "info.banned-until": "Užblokuotas iki %1", + "info.banned-permanently": "Užblokuotas visam laikui", + "info.banned-reason-label": "Priežastis", + "info.banned-no-reason": "Be priežasties" } \ No newline at end of file diff --git a/public/language/lt/users.json b/public/language/lt/users.json index e55086ed4a..ed6ea22471 100644 --- a/public/language/lt/users.json +++ b/public/language/lt/users.json @@ -17,5 +17,5 @@ "unread_topics": "Neperskaitytos temos", "categories": "Kategorijos", "tags": "Žymos", - "no-users-found": "No users found!" + "no-users-found": "Nerasta vartotojų." } \ No newline at end of file diff --git a/public/language/zh_TW/error.json b/public/language/zh_TW/error.json index 954492cdb6..6c45cf3ff3 100644 --- a/public/language/zh_TW/error.json +++ b/public/language/zh_TW/error.json @@ -12,7 +12,7 @@ "invalid-title": "無效的主題!", "invalid-user-data": "無效的使用者資料", "invalid-password": "無效的密碼", - "invalid-username-or-password": "請指定用戶名和密碼", + "invalid-username-or-password": "請指定帳號和密碼", "invalid-search-term": "無效的搜索字詞", "csrf-invalid": "我們無法讓你登入,似乎是因為連線階段已到期。請再重試一次。", "invalid-pagination-value": "無效的分頁數值, 必需是至少 %1 與最多 %2", @@ -20,7 +20,7 @@ "email-taken": "該信箱已被使用", "email-not-confirmed": "你的電子郵件尚未確認,請點擊此處確認你的電子郵件。", "email-not-confirmed-chat": "你需要先確認電子郵件後才能進行聊天,請點擊這裡來確認你的電子郵件。", - "email-not-confirmed-email-sent": "Your email has not been confirmed yet, please check your inbox for the confirmation email.", + "email-not-confirmed-email-sent": "你的電子郵件地址還沒有確認,請確認一下你的收信箱是不是有確認信。", "no-email-to-confirm": "討論區要求電子郵件確認,請點擊這裡輸入一個電子郵件。", "email-confirm-failed": "我們無法確認你的Email,請之後再重試。", "confirm-email-already-sent": "確認電子郵件已經寄送,請等待 %1 分鐘才能再寄送另一封。", @@ -43,18 +43,18 @@ "topic-locked": "該主題已被鎖定", "post-edit-duration-expired": "在張貼 %1 秒後,你才能編輯張貼文章", "post-edit-duration-expired-minutes": "在張貼 %1 秒後,你才能編輯張貼文章", - "post-edit-duration-expired-minutes-seconds": "You are only allowed to edit posts for %1 minute(s) %2 second(s) after posting", - "post-edit-duration-expired-hours": "You are only allowed to edit posts for %1 hour(s) after posting", - "post-edit-duration-expired-hours-minutes": "You are only allowed to edit posts for %1 hour(s) %2 minute(s) after posting", - "post-edit-duration-expired-days": "You are only allowed to edit posts for %1 day(s) after posting", - "post-edit-duration-expired-days-hours": "You are only allowed to edit posts for %1 day(s) %2 hour(s) after posting", - "post-delete-duration-expired": "You are only allowed to delete posts for %1 second(s) after posting", - "post-delete-duration-expired-minutes": "You are only allowed to delete posts for %1 minute(s) after posting", - "post-delete-duration-expired-minutes-seconds": "You are only allowed to delete posts for %1 minute(s) %2 second(s) after posting", - "post-delete-duration-expired-hours": "You are only allowed to delete posts for %1 hour(s) after posting", - "post-delete-duration-expired-hours-minutes": "You are only allowed to delete posts for %1 hour(s) %2 minute(s) after posting", - "post-delete-duration-expired-days": "You are only allowed to delete posts for %1 day(s) after posting", - "post-delete-duration-expired-days-hours": "You are only allowed to delete posts for %1 day(s) %2 hour(s) after posting", + "post-edit-duration-expired-minutes-seconds": "你只被允許在張貼的 %1 分鐘又 %2 秒後才能編輯", + "post-edit-duration-expired-hours": "你只被允許在張貼的 %1 小時後才能編輯", + "post-edit-duration-expired-hours-minutes": "你只被允許在張貼的 %1 小時 %2 分鐘後才能編輯", + "post-edit-duration-expired-days": "你只被允許在張貼的 %1 天後才能編輯", + "post-edit-duration-expired-days-hours": "你只被允許在張貼的 %1 天又 %2 小時後才能編輯", + "post-delete-duration-expired": "你只被允許在張貼的 %1 秒後才能刪除", + "post-delete-duration-expired-minutes": "你只被允許在張貼的 %1 分鐘後才能刪除", + "post-delete-duration-expired-minutes-seconds": "你只被允許在張貼的 %1 分鐘又 %2 秒後才能刪除", + "post-delete-duration-expired-hours": "你只被允許在張貼的 %1 小時後才能刪除", + "post-delete-duration-expired-hours-minutes": "你只被允許在張貼的 %1 小時又 %2 分鐘後才能刪除", + "post-delete-duration-expired-days": "你只被允許在張貼的 %1 天後才能刪除", + "post-delete-duration-expired-days-hours": "你只被允許在張貼的 %1 天又 %2 小時後才能刪除", "cant-delete-topic-has-reply": "你不能刪除你的主題,在它已經有一筆回覆時", "cant-delete-topic-has-replies": "你不能刪除你的主題,在它已經有 %1 筆回覆時", "content-too-short": "請輸入一個長一點的張貼內容。張貼內容長度不能少於 %1 字元。", @@ -74,7 +74,7 @@ "already-unfavourited": "你已經將這篇張貼移除書籤", "cant-ban-other-admins": "你無法封鎖其他的管理員!", "cant-remove-last-admin": "你是唯一的管理員。在你移除自己為管理員前,需要新增另一個使用者為管理員。", - "cant-delete-admin": "Remove administrator privileges from this account before attempting to delete it.", + "cant-delete-admin": "在要刪除這個帳戶前,請先移除這個帳戶的管理員權限", "invalid-image-type": "無效的圖像類型。允許的類型:%1", "invalid-image-extension": "無效的圖像擴充元件", "invalid-file-type": "無效的檔案類型。允許的類型:%1", @@ -98,7 +98,7 @@ "signature-too-long": "抱歉,你的簽名長度不能超過 %1 字元。", "about-me-too-long": "抱歉,關於我長度不能超過 %1 字元。", "cant-chat-with-yourself": "你不能與自己聊天!", - "chat-restricted": "此用戶已限制了他的聊天功能。你要在他關注你之後,才能跟他聊天", + "chat-restricted": "此使用者已限制了他的聊天功能。你要在他(她)關注你之後,才能跟他聊天", "chat-disabled": "聊天系統被禁止", "too-many-messages": "你已經送出過多的訊息,請稍等一下。", "invalid-chat-message": "無效的聊天訊息", @@ -125,5 +125,5 @@ "no-users-selected": "沒有選定使用者", "invalid-home-page-route": "無效的首頁路由", "invalid-session": "會話階段錯誤", - "invalid-session-text": "It looks like your login session is no longer active, or no longer matches with the server. Please refresh this page." + "invalid-session-text": "看起來你的登入會話階段已經無效,或是不符合於伺服器。請重新整理這個頁面。" } \ No newline at end of file diff --git a/public/language/zh_TW/groups.json b/public/language/zh_TW/groups.json index 511331e301..fc2a300e47 100644 --- a/public/language/zh_TW/groups.json +++ b/public/language/zh_TW/groups.json @@ -11,7 +11,7 @@ "pending.none": "目前沒有等待中的會員", "invited.none": "目前沒有邀請的會員", "invited.uninvite": "撤銷邀請", - "invited.search": "搜尋要邀請加入這個群組的用戶", + "invited.search": "搜尋要邀請加入這個群組的使用者", "invited.notification_title": "你已被邀請加入%1", "request.notification_title": "群組會員要求,來自%1", "request.notification_text": "%1已經要求成為%2群組的會員", @@ -39,7 +39,7 @@ "details.userTitleEnabled": "顯示徽章", "details.private_help": "如果開啟,加入群組需要經過群組擁有者批準", "details.hidden": "隱藏", - "details.hidden_help": "如果開啟的話,群組將不會在群組列表中被看到,而且用戶將需要手動邀請", + "details.hidden_help": "如果開啟的話,群組將不會在群組列表中被看到,而且使用者將需要手動邀請", "details.delete_group": "刪除群組", "details.private_system_help": "私有群組在系統層級被禁用,這個選項沒有任何作用", "event.updated": "群組詳細訊息已被更新", diff --git a/public/language/zh_TW/modules.json b/public/language/zh_TW/modules.json index 61d9b00cea..1e688f79c3 100644 --- a/public/language/zh_TW/modules.json +++ b/public/language/zh_TW/modules.json @@ -8,7 +8,7 @@ "chat.see_all": "顯示全部聊天", "chat.mark_all_read": "所有訊息標為已讀", "chat.no-messages": "請選擇收件人來查看聊天記錄", - "chat.no-users-in-room": "沒有用戶在聊天室中", + "chat.no-users-in-room": "沒有使用者在聊天室中", "chat.recent-chats": "最近的聊天記錄", "chat.contacts": "通訊錄", "chat.message-history": "消息記錄", @@ -19,7 +19,7 @@ "chat.three_months": "3個月", "chat.delete_message_confirm": "你確定要刪除這個訊息?", "chat.roomname": "聊天室 %1", - "chat.add-users-to-room": "將用戶加入聊天室中", + "chat.add-users-to-room": "將使用者加入聊天室中", "composer.compose": "撰寫", "composer.show_preview": "顯示預覽", "composer.hide_preview": "隱藏預覽", diff --git a/public/language/zh_TW/pages.json b/public/language/zh_TW/pages.json index a2b1e523ac..caa4a99d18 100644 --- a/public/language/zh_TW/pages.json +++ b/public/language/zh_TW/pages.json @@ -7,13 +7,13 @@ "popular-alltime": "所有時間受歡迎的主題", "recent": "近期的主題", "flagged-posts": "標記的張貼", - "users/online": "線上用戶", - "users/latest": "最近用戶", + "users/online": "線上使用者", + "users/latest": "最近使用者", "users/sort-posts": "最多張貼的使用者", "users/sort-reputation": "最多聲譽的使用者", - "users/banned": "已封鎖用戶", + "users/banned": "已封鎖使用者", "users/most-flags": "最多標註的使用者", - "users/search": "用戶搜尋", + "users/search": "使用者搜尋", "notifications": "新訊息通知", "tags": "標籤", "tag": "有\"%1\"標籤的主題", @@ -36,7 +36,7 @@ "account/topics": "由 %1 建立的主題", "account/groups": "%1 的群組", "account/favourites": "%1 所加入書籤的張貼", - "account/settings": "用戶設定", + "account/settings": "使用者設定", "account/watched": "%1 所觀看的主題", "account/upvoted": "%1 所正向投票的張貼", "account/downvoted": "%1 所負向投票的張貼", diff --git a/public/language/zh_TW/register.json b/public/language/zh_TW/register.json index 8ab8daca33..9369d59865 100644 --- a/public/language/zh_TW/register.json +++ b/public/language/zh_TW/register.json @@ -1,13 +1,13 @@ { "register": "註冊", "cancel_registration": "取消註冊", - "help.email": "默認情況下,你的郵箱不會公開。", + "help.email": "在預設情況下,你的電子郵件地址不會被公開。", "help.username_restrictions": "獨立的帳號由 %1 到 %2 個字元組成。其他人可以通過 @帳號 提及你。", "help.minimum_password_length": "密碼必須至少包含 %1 個字元。", "email_address": "Email", - "email_address_placeholder": "輸入郵箱地址", - "username": "用戶名", - "username_placeholder": "輸入用戶名", + "email_address_placeholder": "輸入電子郵件地址", + "username": "帳號", + "username_placeholder": "輸入帳號", "password": "密碼", "password_placeholder": "輸入密碼", "confirm_password": "確認密碼", diff --git a/public/language/zh_TW/topic.json b/public/language/zh_TW/topic.json index de06ca59c4..f6a2fe8c46 100644 --- a/public/language/zh_TW/topic.json +++ b/public/language/zh_TW/topic.json @@ -31,7 +31,7 @@ "bookmark_instructions": "點擊這裡返回到這個討論串的最後一篇張貼文", "flag_title": "檢舉這篇文章, 交給仲裁者來審閱.", "flag_success": "這文章已經被檢舉要求仲裁.", - "deleted_message": "此主題已被刪除。只有具有主題管理權限的用戶才能看到它。", + "deleted_message": "此主題已被刪除。只有具有主題管理權限的使用者才能看到它。", "following_topic.message": "有人貼文回覆主題時, 你將會收到新通知.", "not_following_topic.message": "你將會看到這個主題在未讀主題列表中出現,但你將不會在其他人張貼到這個主題時接收到通知。", "ignoring_topic.message": "你將不會再未讀主題列表中看到這個主題。當你被提及或你的張貼被正向投票時,你會被通知。", @@ -102,8 +102,8 @@ "composer.thumb_file_label": "或上傳檔案", "composer.thumb_remove": "清除所有欄目", "composer.drag_and_drop_images": "拖曳影像到此", - "more_users_and_guests": "%1 個用戶和 %2個訪客", - "more_users": "%1 個用戶", + "more_users_and_guests": "%1 個使用者和 %2個訪客", + "more_users": "%1 個使用者", "more_guests": "%1 個訪客", "users_and_others": "%1 和另外 %2 個人", "sort_by": "排序方式", diff --git a/public/language/zh_TW/user.json b/public/language/zh_TW/user.json index f23c2414a5..578b39056a 100644 --- a/public/language/zh_TW/user.json +++ b/public/language/zh_TW/user.json @@ -11,7 +11,7 @@ "ban_account_confirm": "你確定要禁用這個使用者?", "unban_account": "取消禁用帳號", "delete_account": "刪除帳戶", - "delete_account_confirm": "你確定要刪除自己的帳戶?
此操作不能復原,你將無法恢復任何數據

輸入你的帳號,用來確認希望刪除這個帳戶。", + "delete_account_confirm": "你確定要刪除自己的帳戶?
此操作不能復原,你將無法恢復任何資料

輸入你的帳號,來確認你希望刪除這個帳戶。", "delete_this_account_confirm": "你確定要刪除這個帳戶?
此操作是不能還原的,你將無法回復任何資料
", "account-deleted": "帳號已刪除", "fullname": "全名", @@ -63,9 +63,9 @@ "remove_uploaded_picture": "移除上傳的圖片", "upload_cover_picture": "上傳封面圖片", "settings": "設定", - "show_email": "顯示我的郵箱", + "show_email": "顯示我的電子郵件地址", "show_fullname": "顯示我的全名", - "restrict_chats": "只允許我跟隨的用戶和我聊天", + "restrict_chats": "只允許我跟隨的使用者和我聊天", "digest_label": "訂閱摘要", "digest_description": "根據你所設的時間排程,用電子郵件訂閱這個討論區 (新的通知與主題)。", "digest_off": "關閉", @@ -75,15 +75,15 @@ "send_chat_notifications": "如果有新的聊天消息而我不在線,發送郵件給我", "send_post_notifications": "當我訂閱的主題有新回覆時寄送Email給我", "settings-require-reload": "有些設定的更動是需要重新整理。點擊這裡來重新整理頁面。", - "has_no_follower": "該用戶還沒有被任何人關注。", - "follows_no_one": "該用戶還沒有關注過任何人。", + "has_no_follower": "該使用者還沒有被任何人關注。", + "follows_no_one": "該使用者還沒有關注過任何人。", "has_no_posts": "使用者還沒有發表任何張貼", "has_no_topics": "使用者還沒有發表任何主題", "has_no_watched_topics": "使用者還沒有觀看任何主題", "has_no_upvoted_posts": "使用者還沒有對任何主題投正向票", "has_no_downvoted_posts": "使用者還沒有對任何主題投負向票", "has_no_voted_posts": "這個使用者沒有投票的張貼", - "email_hidden": "郵箱被隱藏", + "email_hidden": "電子郵件地址被隱藏", "hidden": "隱藏", "paginate_description": "將主題與張貼用分頁來顯示,取代使用無盡的捲動方式。", "topics_per_page": "每頁的主題數", @@ -119,6 +119,6 @@ "info.no-ban-history": "這個使用者永遠不會被禁用", "info.banned-until": "禁用至 %1", "info.banned-permanently": "永久禁用", - "info.banned-reason-label": "Reason", - "info.banned-no-reason": "No reason given." + "info.banned-reason-label": "理由", + "info.banned-no-reason": "沒有給理由" } \ No newline at end of file diff --git a/public/language/zh_TW/users.json b/public/language/zh_TW/users.json index e53abef233..dc08b3056e 100644 --- a/public/language/zh_TW/users.json +++ b/public/language/zh_TW/users.json @@ -1,5 +1,5 @@ { - "latest_users": "最近用戶", + "latest_users": "最近使用者", "top_posters": "發文數最多", "most_reputation": "聲望最高", "most_flags": "最多標註", @@ -11,11 +11,11 @@ "online-only": "線上僅有", "invite": "邀請", "invitation-email-sent": "所有邀請Email已經被寄送到 %1", - "user_list": "用戶列表", + "user_list": "使用者列表", "recent_topics": "最新的主題", "popular_topics": "受歡迎的主題", "unread_topics": "未讀的主題", "categories": "類別", "tags": "標籤", - "no-users-found": "沒有找到用戶!" + "no-users-found": "沒有找到使用者!" } \ No newline at end of file From 46d216891838f677eb09644cfd597ae463530899 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Wed, 7 Sep 2016 18:48:00 +0300 Subject: [PATCH 07/28] closes #5017 --- src/socket.io/modules.js | 30 ++++++++++++++++-------------- src/views/admin/settings/chat.tpl | 6 ++++++ 2 files changed, 22 insertions(+), 14 deletions(-) diff --git a/src/socket.io/modules.js b/src/socket.io/modules.js index 954d407d5f..b77d22252d 100644 --- a/src/socket.io/modules.js +++ b/src/socket.io/modules.js @@ -53,13 +53,9 @@ SocketModules.chats.newRoom = function(socket, data, callback) { if (!data) { return callback(new Error('[[error:invalid-data]]')); } - var now = Date.now(); - // Websocket rate limiting - socket.lastChatMessageTime = socket.lastChatMessageTime || 0; - if (now - socket.lastChatMessageTime < 200) { + + if (rateLimitExceeded(socket)) { return callback(new Error('[[error:too-many-messages]]')); - } else { - socket.lastChatMessageTime = now; } Messaging.canMessageUser(socket.uid, data.touid, function(err) { @@ -76,14 +72,8 @@ SocketModules.chats.send = function(socket, data, callback) { return callback(new Error('[[error:invalid-data]]')); } - var now = Date.now(); - - // Websocket rate limiting - socket.lastChatMessageTime = socket.lastChatMessageTime || 0; - if (now - socket.lastChatMessageTime < 200) { + if (rateLimitExceeded(socket)) { return callback(new Error('[[error:too-many-messages]]')); - } else { - socket.lastChatMessageTime = now; } async.waterfall([ @@ -100,7 +90,7 @@ SocketModules.chats.send = function(socket, data, callback) { Messaging.canMessageRoom(socket.uid, data.roomId, next); }, function (next) { - Messaging.sendMessage(socket.uid, data.roomId, data.message, now, next); + Messaging.sendMessage(socket.uid, data.roomId, data.message, Date.now(), next); }, function (message, next) { Messaging.notifyUsersInRoom(socket.uid, data.roomId, message); @@ -110,6 +100,18 @@ SocketModules.chats.send = function(socket, data, callback) { ], callback); }; +function rateLimitExceeded(socket) { + var now = Date.now(); + socket.lastChatMessageTime = socket.lastChatMessageTime || 0; + var delay = meta.config.hasOwnProperty('chatMessageDelay') ? parseInt(meta.config.chatMessageDelay, 10) : 200; + if (now - socket.lastChatMessageTime < delay) { + return true; + } else { + socket.lastChatMessageTime = now; + } + return false; +} + SocketModules.chats.loadRoom = function(socket, data, callback) { if (!data || !data.roomId) { return callback(new Error('[[error:invalid-data]]')); diff --git a/src/views/admin/settings/chat.tpl b/src/views/admin/settings/chat.tpl index 9926e25dbe..5f4d0b8315 100644 --- a/src/views/admin/settings/chat.tpl +++ b/src/views/admin/settings/chat.tpl @@ -22,6 +22,12 @@ + + +
+ + +
From 7269d45e846d86dfb384b03848f72c2891b5d10f Mon Sep 17 00:00:00 2001 From: barisusakli Date: Wed, 7 Sep 2016 19:27:29 +0300 Subject: [PATCH 08/28] closes #5018 --- src/install.js | 8 +++----- src/meta/configs.js | 15 ++++++++++----- src/meta/settings.js | 14 ++++++++++---- 3 files changed, 23 insertions(+), 14 deletions(-) diff --git a/src/install.js b/src/install.js index 8cf99ba264..0e97d8f8fe 100644 --- a/src/install.js +++ b/src/install.js @@ -179,12 +179,10 @@ function completeConfigSetup(err, config, next) { function setupDefaultConfigs(next) { process.stdout.write('Populating database with default configs, if not already set...\n'); - var meta = require('./meta'), - defaults = require(path.join(__dirname, '../', 'install/data/defaults.json')); + var meta = require('./meta'); + var defaults = require(path.join(__dirname, '../', 'install/data/defaults.json')); - async.eachSeries(Object.keys(defaults), function (key, next) { - meta.configs.setOnEmpty(key, defaults[key], next); - }, function (err) { + meta.configs.setOnEmpty(key, defaults, function (err) { if (err) { return next(err); } diff --git a/src/meta/configs.js b/src/meta/configs.js index 61464c232a..bbb5b3a387 100644 --- a/src/meta/configs.js +++ b/src/meta/configs.js @@ -118,14 +118,19 @@ module.exports = function(Meta) { } }); - Meta.configs.setOnEmpty = function (field, value, callback) { - Meta.configs.get(field, function (err, curValue) { + Meta.configs.setOnEmpty = function (values, callback) { + db.getObject('config', function(err, data) { if (err) { return callback(err); } - - if (!curValue) { - Meta.configs.set(field, value, callback); + var empty = {}; + Object.keys(values).forEach(function(key) { + if (!data.hasOwnProperty(key)) { + empty[key] = values[key]; + } + }); + if (Object.keys(empty).length) { + db.setObject('config', empty, callback); } else { callback(); } diff --git a/src/meta/settings.js b/src/meta/settings.js index ed6702a943..6f14a33a8a 100644 --- a/src/meta/settings.js +++ b/src/meta/settings.js @@ -38,14 +38,20 @@ module.exports = function(Meta) { db.setObjectField('settings:' + hash, field, value, callback); }; - Meta.settings.setOnEmpty = function (hash, field, value, callback) { - Meta.settings.getOne(hash, field, function (err, curValue) { + Meta.settings.setOnEmpty = function (hash, values, callback) { + db.getObject('settings:' + hash, function(err, settings) { if (err) { return callback(err); } + var empty = {}; + Object.keys(values).forEach(function(key) { + if (!settings.hasOwnProperty(key)) { + empty[key] = values[key]; + } + }); - if (!curValue) { - Meta.settings.setOne(hash, field, value, callback); + if (Object.keys(empty).length) { + db.setObject('settings:' + hash, empty, callback); } else { callback(); } From 3c0a28d29354cf143dd0d0c2e884b42dfc1a7e28 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Wed, 7 Sep 2016 19:29:25 +0300 Subject: [PATCH 09/28] #5018 fix extra key --- src/install.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/install.js b/src/install.js index 0e97d8f8fe..5e642d0177 100644 --- a/src/install.js +++ b/src/install.js @@ -182,7 +182,7 @@ function setupDefaultConfigs(next) { var meta = require('./meta'); var defaults = require(path.join(__dirname, '../', 'install/data/defaults.json')); - meta.configs.setOnEmpty(key, defaults, function (err) { + meta.configs.setOnEmpty(defaults, function (err) { if (err) { return next(err); } From d4c512cb3fbb5da741b3f5ade1e66b6f5b4d5630 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Wed, 7 Sep 2016 19:34:59 +0300 Subject: [PATCH 10/28] handle the case where the object is not set at all #5018 --- src/meta/configs.js | 1 + src/meta/settings.js | 1 + 2 files changed, 2 insertions(+) diff --git a/src/meta/configs.js b/src/meta/configs.js index bbb5b3a387..723e58083d 100644 --- a/src/meta/configs.js +++ b/src/meta/configs.js @@ -123,6 +123,7 @@ module.exports = function(Meta) { if (err) { return callback(err); } + data = data || {}; var empty = {}; Object.keys(values).forEach(function(key) { if (!data.hasOwnProperty(key)) { diff --git a/src/meta/settings.js b/src/meta/settings.js index 6f14a33a8a..4fba3fcb2e 100644 --- a/src/meta/settings.js +++ b/src/meta/settings.js @@ -43,6 +43,7 @@ module.exports = function(Meta) { if (err) { return callback(err); } + settings = settings || {}; var empty = {}; Object.keys(values).forEach(function(key) { if (!settings.hasOwnProperty(key)) { From 8273dab3680bbe6ca75aef55b9da5411e2f15a72 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Thu, 8 Sep 2016 14:01:20 +0300 Subject: [PATCH 11/28] ability to pass tags to new topics --- public/src/app.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/public/src/app.js b/public/src/app.js index 0e357dc50e..b714a441cd 100644 --- a/public/src/app.js +++ b/public/src/app.js @@ -488,9 +488,10 @@ app.cacheBuster = null; }); }; - app.newTopic = function (cid) { + app.newTopic = function (cid, tags) { $(window).trigger('action:composer.topic.new', { - cid: cid || ajaxify.data.cid || 0 + cid: cid || ajaxify.data.cid || 0, + tags: tags || (ajaxify.data.tag ? [ajaxify.data.tag] : []) }); }; From bba3c0623e481cfe3c23be9afeb04280f5b97438 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Thu, 8 Sep 2016 14:03:48 +0300 Subject: [PATCH 12/28] up themes --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 70bf0494e4..06bd6b0d31 100644 --- a/package.json +++ b/package.json @@ -60,8 +60,8 @@ "nodebb-plugin-spam-be-gone": "0.4.10", "nodebb-rewards-essentials": "0.0.9", "nodebb-theme-lavender": "3.0.14", - "nodebb-theme-persona": "4.1.42", - "nodebb-theme-vanilla": "5.1.26", + "nodebb-theme-persona": "4.1.43", + "nodebb-theme-vanilla": "5.1.27", "nodebb-widget-essentials": "2.0.10", "nodemailer": "2.0.0", "nodemailer-sendmail-transport": "1.0.0", From b2aaeaa50d61e5bbfdec6775155cb16e140ec6db Mon Sep 17 00:00:00 2001 From: barisusakli Date: Thu, 8 Sep 2016 14:13:41 +0300 Subject: [PATCH 13/28] up composer --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 06bd6b0d31..078fd75b1a 100644 --- a/package.json +++ b/package.json @@ -50,7 +50,7 @@ "morgan": "^1.3.2", "mousetrap": "^1.5.3", "nconf": "~0.8.2", - "nodebb-plugin-composer-default": "4.2.2", + "nodebb-plugin-composer-default": "4.2.3", "nodebb-plugin-dbsearch": "1.0.2", "nodebb-plugin-emoji-extended": "1.1.1", "nodebb-plugin-emoji-one": "1.1.5", From d0a94cb86cf508c1ef8b30d5f29b86771ddf744d Mon Sep 17 00:00:00 2001 From: barisusakli Date: Thu, 8 Sep 2016 15:59:10 +0300 Subject: [PATCH 14/28] filter:topics.searchTags --- src/topics/tags.js | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/src/topics/tags.js b/src/topics/tags.js index 13dcd4b14d..50db8ccede 100644 --- a/src/topics/tags.js +++ b/src/topics/tags.js @@ -263,13 +263,29 @@ module.exports = function(Topics) { }; Topics.searchTags = function(data, callback) { + function done(matches) { + plugins.fireHook('filter:tags.search', {data: data, matches: matches}, function(err, data) { + callback(err, data ? data.matches : []); + }); + } + + if (!data || !data.query) { return callback(null, []); } + if (plugins.hasListeners('filter:topics.searchTags')) { + return plugins.fireHook('filter:topics.searchTags', {data: data}, function(err, data) { + if (err) { + return callback(err); + } + done(data.matches); + }); + } + db.getSortedSetRevRange('tags:topic:count', 0, -1, function(err, tags) { if (err) { - return callback(null, []); + return callback(err); } data.query = data.query.toLowerCase(); @@ -285,9 +301,7 @@ module.exports = function(Topics) { return a > b; }); - plugins.fireHook('filter:tags.search', {data: data, matches: matches}, function(err, data) { - callback(err, data ? data.matches : []); - }); + done(matches); }); }; From 22ab41f99813546656bb21721e860fe78f2fbb88 Mon Sep 17 00:00:00 2001 From: NodeBB Misty Date: Thu, 8 Sep 2016 09:03:24 -0400 Subject: [PATCH 15/28] Latest translations and fallbacks --- public/language/fa_IR/user.json | 14 +++++++------- public/language/fa_IR/users.json | 14 +++++++------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/public/language/fa_IR/user.json b/public/language/fa_IR/user.json index 639bed11f4..acccf82a6d 100644 --- a/public/language/fa_IR/user.json +++ b/public/language/fa_IR/user.json @@ -1,20 +1,20 @@ { - "banned": "اخراج شده", + "banned": "مسدود شده", "offline": "آفلاین", "username": "نام کاربری", "joindate": "زمان عضویت", - "postcount": "تعداد پست ها", + "postcount": "تعداد پست‌ها", "email": "رایانامه", "confirm_email": "تأیید ایمیل", "account_info": "اطلاعات شناسه کاربری", - "ban_account": "مسدود کردن حساب کاربری", + "ban_account": "مسدود کردن", "ban_account_confirm": "از مسدود کردن این کاربر اطمینان دارید؟", "unban_account": "آزاد کردن حساب کاربری", "delete_account": "حذف حساب کاربری", "delete_account_confirm": "آیا مطمئنید که میخواهید حساب کاربری خود را حذف کنید؟
این عمل غیر قابل بازگشت است و شما قادر نخواهید بود هیچ کدام از اطلاعات خود را بازیابی کنید./strong>

برای تایید حذف این حساب کاربری، نام کاربری خود را وارد کنید", "delete_this_account_confirm": "آیا مطمئنید که میخواهید این حساب کاربری را حذف کنید؟
این عمل غیر قابل بازگشت است و شما قادر نخواهید بود هیچ کدام از اطلاعات را بازیابی کنید.

", "account-deleted": "حساب کاربری پاک شد", - "fullname": "نام و نام خانوادگی", + "fullname": "نام و نام‌خانوادگی", "website": "تارنما", "location": "محل سکونت", "age": "سن", @@ -63,11 +63,11 @@ "remove_uploaded_picture": "پاک کردن عکس بارگذاری شده", "upload_cover_picture": "بارگذاری عکس کاور", "settings": "تنظیمات", - "show_email": "نمایش ایمیل‌ام", + "show_email": "نمایش ایمیل‌های من", "show_fullname": "نام کامل من را نشان بده", - "restrict_chats": "قبول پیام فقط ازکاربرانی که من را دنبال میکنند", + "restrict_chats": "قبول پیام فقط ازکاربرانی که آن‌ها را دنبال میکنم", "digest_label": "مشترک شدن در چکیده", - "digest_description": "مشترک شدن برای دریافت تازه‌هی این انجمن (موضوع ها و آکاه‌سازی‌های تازه) با ایمیل روی یک برنامه زمان‌بندی", + "digest_description": "مشترک شدن برای دریافت جدیدترین‌های این انجمن (موضوع ها و آکاه‌سازی‌های تازه) با ایمیل روی یک برنامه زمان‌بندی", "digest_off": "خاموش", "digest_daily": "روزانه", "digest_weekly": "هفتگی", diff --git a/public/language/fa_IR/users.json b/public/language/fa_IR/users.json index 1bb821706d..81b664bbb1 100644 --- a/public/language/fa_IR/users.json +++ b/public/language/fa_IR/users.json @@ -1,21 +1,21 @@ { "latest_users": "آخرین کاربران", - "top_posters": "بهترین فرستنده‌ها", + "top_posters": "برترین فرستنده‌ها", "most_reputation": "بیش‌ترین اعتبار", "most_flags": "بیشترین پرچم‌ها", "search": "جستجو", "enter_username": "یک نام کاربری برای جستجو وارد کنید", "load_more": "بارگذاری بیش‌تر", - "users-found-search-took": "%1 کاربر(ها) یافت شد! جستجو %2 ثانیه طولید", + "users-found-search-took": "%1 کاربر(ها) یافت شد! جستجو %2 ثانیه زمان گرفت.", "filter-by": "فیلتر با", "online-only": "فقط آنلاین", "invite": "دعوت", - "invitation-email-sent": "ایمیل ی دعوتنامه به %1 ارسال شد", + "invitation-email-sent": "ایمیل دعوتنامه برای %1 ارسال شد", "user_list": "فهرست کاربران", - "recent_topics": "موضوع های اخیر", - "popular_topics": "موضوع های پربازدید", - "unread_topics": "موضوع های خوانده نشده", - "categories": "دسته ها", + "recent_topics": "موضوع‌های اخیر", + "popular_topics": "موضوع‌های پربازدید", + "unread_topics": "موضوع‌های خوانده نشده", + "categories": "دسته‌ها", "tags": "برچسب‌ها", "no-users-found": "کاربری پیدا نشد!" } \ No newline at end of file From fdf2cb6f8188d28e399c6cdc593eca86e70ae38d Mon Sep 17 00:00:00 2001 From: barisusakli Date: Thu, 8 Sep 2016 16:22:17 +0300 Subject: [PATCH 16/28] stop searching after finding 20 --- src/topics/tags.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/topics/tags.js b/src/topics/tags.js index 50db8ccede..4d8e01b912 100644 --- a/src/topics/tags.js +++ b/src/topics/tags.js @@ -294,10 +294,13 @@ module.exports = function(Topics) { for(var i=0; i 19) { + break; + } } } - matches = matches.slice(0, 20).sort(function(a, b) { + matches = matches.sort(function(a, b) { return a > b; }); From 8d897d8dcf317047f21286bb279f9e3d3a57a559 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Thu, 8 Sep 2016 17:24:20 +0300 Subject: [PATCH 17/28] autocompleteTags --- src/socket.io/topics/tags.js | 4 ++++ src/topics/tags.js | 35 ++++++++++++++++++++++++++++++----- 2 files changed, 34 insertions(+), 5 deletions(-) diff --git a/src/socket.io/topics/tags.js b/src/socket.io/topics/tags.js index f55ec377fb..54bad0e7b3 100644 --- a/src/socket.io/topics/tags.js +++ b/src/socket.io/topics/tags.js @@ -4,6 +4,10 @@ var topics = require('../../topics'); var utils = require('../../../public/src/utils'); module.exports = function(SocketTopics) { + SocketTopics.autocompleteTags = function(socket, data, callback) { + topics.autocompleteTags(data, callback); + }; + SocketTopics.searchTags = function(socket, data, callback) { topics.searchTags(data, callback); }; diff --git a/src/topics/tags.js b/src/topics/tags.js index 4d8e01b912..ee5f7d8b9d 100644 --- a/src/topics/tags.js +++ b/src/topics/tags.js @@ -283,16 +283,42 @@ module.exports = function(Topics) { }); } + findMatches(data.query, function(err, matches) { + if (err) { + return callback(err); + } + done(matches); + }); + }; + + Topics.autocompleteTags = function(data, callback) { + if (!data || !data.query) { + return callback(null, []); + } + + if (plugins.hasListeners('filter:topics.autocompleteTags')) { + return plugins.fireHook('filter:topics.autocompleteTags', {data: data}, function(err, data) { + if (err) { + return callback(err); + } + callback(null, data.matches); + }); + } + + findMatches(data.query, callback); + }; + + function findMatches(query, callback) { db.getSortedSetRevRange('tags:topic:count', 0, -1, function(err, tags) { if (err) { return callback(err); } - data.query = data.query.toLowerCase(); + query = query.toLowerCase(); var matches = []; for(var i=0; i 19) { break; @@ -303,10 +329,9 @@ module.exports = function(Topics) { matches = matches.sort(function(a, b) { return a > b; }); - - done(matches); + callback(null, matches); }); - }; + } Topics.searchAndLoadTags = function(data, callback) { var searchResult = { From eabb745116a77a3573a734d76011ffe8cee7e204 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Thu, 8 Sep 2016 17:59:59 +0300 Subject: [PATCH 18/28] change union to match intersect accept aggregate and withscores --- src/database/mongo/sorted.js | 48 ++++++++++++++++++++++++------------ src/database/redis/sorted.js | 22 +++++++++-------- tests/database/sorted.js | 4 +-- 3 files changed, 46 insertions(+), 28 deletions(-) diff --git a/src/database/mongo/sorted.js b/src/database/mongo/sorted.js index 33e6d4e79b..4969fd8030 100644 --- a/src/database/mongo/sorted.js +++ b/src/database/mongo/sorted.js @@ -474,48 +474,64 @@ module.exports = function(db, module) { }); }; - module.getSortedSetUnion = function(sets, start, stop, callback) { - getSortedSetUnion(sets, 1, start, stop, callback); + module.getSortedSetUnion = function(params, callback) { + params.sort = 1; + getSortedSetUnion(params, callback); }; - module.getSortedSetRevUnion = function(sets, start, stop, callback) { - getSortedSetUnion(sets, -1, start, stop, callback); + module.getSortedSetRevUnion = function(params, callback) { + params.sort = -1; + getSortedSetUnion(params, callback); }; - function getSortedSetUnion(sets, sort, start, stop, callback) { - if (!Array.isArray(sets) || !sets.length) { + function getSortedSetUnion(params, callback) { + if (!Array.isArray(params.sets) || !params.sets.length) { return callback(); } - var limit = stop - start + 1; + var limit = params.stop - params.start + 1; if (limit <= 0) { limit = 0; } + var aggregate = {}; + if (params.aggregate) { + aggregate['$' + params.aggregate.toLowerCase()] = '$score'; + } else { + aggregate.$sum = '$score'; + } + var pipeline = [ - { $match: { _key: {$in: sets}} }, - { $group: { _id: {value: '$value'}, totalScore: {$sum : "$score"}} }, - { $sort: { totalScore: sort} } + { $match: { _key: {$in: params.sets}} }, + { $group: { _id: {value: '$value'}, totalScore: aggregate} }, + { $sort: { totalScore: params.sort} } ]; - if (start) { - pipeline.push({ $skip: start }); + if (params.start) { + pipeline.push({ $skip: params.start }); } if (limit > 0) { pipeline.push({ $limit: limit }); } - pipeline.push({ $project: { _id: 0, value: '$_id.value' }}); + var project = { _id: 0, value: '$_id.value' }; + if (params.withScores) { + project.score = '$totalScore'; + } + pipeline.push({ $project: project }); db.collection('objects').aggregate(pipeline, function(err, data) { if (err || !data) { return callback(err); } - data = data.map(function(item) { - return item.value; - }); + if (!params.withScores) { + data = data.map(function(item) { + return item.value; + }); + } + callback(null, data); }); } diff --git a/src/database/redis/sorted.js b/src/database/redis/sorted.js index eaa3b2430f..20a3d7b0ff 100644 --- a/src/database/redis/sorted.js +++ b/src/database/redis/sorted.js @@ -232,32 +232,34 @@ module.exports = function(redisClient, module) { multi.exec(callback); }; - module.getSortedSetUnion = function(sets, start, stop, callback) { - sortedSetUnion('zrange', sets, start, stop, false, callback); + module.getSortedSetUnion = function(params, callback) { + params.method = 'zrange'; + sortedSetUnion(params, callback); }; - module.getSortedSetRevUnion = function(sets, start, stop, callback) { - sortedSetUnion('zrevrange', sets, start, stop, false, callback); + module.getSortedSetRevUnion = function(params, callback) { + params.method = 'zrevrange'; + sortedSetUnion(params, callback); }; - function sortedSetUnion(method, sets, start, stop, withScores, callback) { + function sortedSetUnion(params, callback) { var tempSetName = 'temp_' + Date.now(); - var params = [tempSetName, start, stop]; - if (withScores) { + var rangeParams = [tempSetName, params.start, params.stop]; + if (params.withScores) { params.push('WITHSCORES'); } var multi = redisClient.multi(); - multi.zunionstore([tempSetName, sets.length].concat(sets)); - multi[method](params); + multi.zunionstore([tempSetName, params.sets.length].concat(params.sets)); + multi[params.method](rangeParams); multi.del(tempSetName); multi.exec(function(err, results) { if (err) { return callback(err); } - if (!withScores) { + if (!params.withScores) { return callback(null, results ? results[1] : null); } results = results[1] || []; diff --git a/tests/database/sorted.js b/tests/database/sorted.js index c1066f5ff4..9f9d72f580 100644 --- a/tests/database/sorted.js +++ b/tests/database/sorted.js @@ -438,7 +438,7 @@ describe('Sorted Set methods', function() { describe('getSortedSetUnion()', function() { it('should return an array of values from both sorted sets sorted by scores lowest to highest', function(done) { - db.getSortedSetUnion(['sortedSetTest2', 'sortedSetTest3'], 0, -1, function(err, values) { + db.getSortedSetUnion({sets: ['sortedSetTest2', 'sortedSetTest3'], start: 0, stop: -1}, function(err, values) { assert.equal(err, null); assert.equal(arguments.length, 2); assert.deepEqual(values, ['value1', 'value2', 'value4']); @@ -449,7 +449,7 @@ describe('Sorted Set methods', function() { describe('getSortedSetRevUnion()', function() { it('should return an array of values from both sorted sets sorted by scores highest to lowest', function(done) { - db.getSortedSetRevUnion(['sortedSetTest2', 'sortedSetTest3'], 0, -1, function(err, values) { + db.getSortedSetRevUnion({sets: ['sortedSetTest2', 'sortedSetTest3'], start: 0, stop: -1}, function(err, values) { assert.equal(err, null); assert.equal(arguments.length, 2); assert.deepEqual(values, ['value4', 'value2', 'value1']); From 650d74e28baa32a7bfcaada9fa3c3d0557a0d31b Mon Sep 17 00:00:00 2001 From: barisusakli Date: Thu, 8 Sep 2016 18:02:22 +0300 Subject: [PATCH 19/28] up composer --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 078fd75b1a..9bd9e7406f 100644 --- a/package.json +++ b/package.json @@ -50,7 +50,7 @@ "morgan": "^1.3.2", "mousetrap": "^1.5.3", "nconf": "~0.8.2", - "nodebb-plugin-composer-default": "4.2.3", + "nodebb-plugin-composer-default": "4.2.4", "nodebb-plugin-dbsearch": "1.0.2", "nodebb-plugin-emoji-extended": "1.1.1", "nodebb-plugin-emoji-one": "1.1.5", From 7f27a21441ac1cc2c7239b903a8a3564c6b21a9d Mon Sep 17 00:00:00 2001 From: barisusakli Date: Fri, 9 Sep 2016 11:48:27 +0300 Subject: [PATCH 20/28] new hook action:infinitescroll.loadmore --- public/src/client/infinitescroll.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/public/src/client/infinitescroll.js b/public/src/client/infinitescroll.js index 1823b15ca3..aa559c623e 100644 --- a/public/src/client/infinitescroll.js +++ b/public/src/client/infinitescroll.js @@ -52,7 +52,11 @@ define('forum/infinitescroll', function() { return; } loadingMore = true; - socket.emit(method, data, function(err, data) { + + var hookData = {method: method, data: data}; + $(window).trigger('action:infinitescroll.loadmore', hookData); + + socket.emit(hookData.method, hookData.data, function(err, data) { if (err) { loadingMore = false; return app.alertError(err.message); From 77c549081c49c40f86573d7043b5dfdebdc53a34 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Fri, 9 Sep 2016 13:13:59 +0300 Subject: [PATCH 21/28] remove unused clearRequireCache, closes #5021 --- src/plugins.js | 27 --------------------------- 1 file changed, 27 deletions(-) diff --git a/src/plugins.js b/src/plugins.js index fc56d799c6..0d6bb02fb5 100644 --- a/src/plugins.js +++ b/src/plugins.js @@ -406,31 +406,4 @@ var middleware; ], callback); }; - Plugins.clearRequireCache = function(next) { - var cached = Object.keys(require.cache); - async.waterfall([ - async.apply(async.map, Plugins.libraryPaths, fs.realpath), - function(paths, next) { - paths = paths.map(function(pluginLib) { - var parent = path.dirname(pluginLib); - return cached.filter(function(libPath) { - return libPath.indexOf(parent) !== -1; - }); - }).reduce(function(prev, cur) { - return prev.concat(cur); - }); - - Plugins.fireHook('filter:plugins.clearRequireCache', {paths: paths}, next); - }, - function(data, next) { - for (var x=0,numPaths=data.paths.length;x Date: Fri, 9 Sep 2016 13:19:14 +0300 Subject: [PATCH 22/28] sortedSetUnionCard --- src/database/mongo/sorted.js | 23 +++++++++++++++++++++-- src/database/redis/sorted.js | 17 +++++++++++++++++ tests/database/sorted.js | 10 ++++++++++ 3 files changed, 48 insertions(+), 2 deletions(-) diff --git a/src/database/mongo/sorted.js b/src/database/mongo/sorted.js index 4969fd8030..237a33deee 100644 --- a/src/database/mongo/sorted.js +++ b/src/database/mongo/sorted.js @@ -474,6 +474,26 @@ module.exports = function(db, module) { }); }; + module.sortedSetUnionCard = function(keys, callback) { + if (!Array.isArray(keys) || !keys.length) { + return callback(null, 0); + } + + var pipeline = [ + { $match: { _key: {$in: keys} } }, + { $group: { _id: {value: '$value' } } }, + { $group: { _id: null, count: { $sum: 1 } } } + ]; + + var project = { _id: 0, count: '$count' }; + pipeline.push({ $project: project }); + + db.collection('objects').aggregate(pipeline, function(err, data) { + console.log(data); + callback(err, Array.isArray(data) && data.length ? data[0].count : 0); + }); + }; + module.getSortedSetUnion = function(params, callback) { params.sort = 1; getSortedSetUnion(params, callback); @@ -484,7 +504,6 @@ module.exports = function(db, module) { getSortedSetUnion(params, callback); }; - function getSortedSetUnion(params, callback) { if (!Array.isArray(params.sets) || !params.sets.length) { return callback(); @@ -629,7 +648,7 @@ module.exports = function(db, module) { getSortedSetRevIntersect(params, callback); }; - function getSortedSetRevIntersect (params, callback) { + function getSortedSetRevIntersect(params, callback) { var sets = params.sets; var start = params.hasOwnProperty('start') ? params.start : 0; var stop = params.hasOwnProperty('stop') ? params.stop : -1; diff --git a/src/database/redis/sorted.js b/src/database/redis/sorted.js index 20a3d7b0ff..f39189cf0f 100644 --- a/src/database/redis/sorted.js +++ b/src/database/redis/sorted.js @@ -232,6 +232,23 @@ module.exports = function(redisClient, module) { multi.exec(callback); }; + + module.sortedSetUnionCard = function(keys, callback) { + var tempSetName = 'temp_' + Date.now(); + + var multi = redisClient.multi(); + multi.zunionstore([tempSetName, keys.length].concat(keys)); + multi.zcard(tempSetName); + multi.del(tempSetName); + multi.exec(function(err, results) { + if (err) { + return callback(err); + } + + callback(null, Array.isArray(results) && results.length ? results[1] : 0); + }); + }; + module.getSortedSetUnion = function(params, callback) { params.method = 'zrange'; sortedSetUnion(params, callback); diff --git a/tests/database/sorted.js b/tests/database/sorted.js index 9f9d72f580..91da2c657c 100644 --- a/tests/database/sorted.js +++ b/tests/database/sorted.js @@ -436,6 +436,16 @@ describe('Sorted Set methods', function() { }); }); + describe('sortedSetUnionCard', function() { + it('should return the number of elements in the union', function(done) { + db.sortedSetUnionCard(['sortedSetTest2', 'sortedSetTest3'], function(err, count) { + assert.ifError(err); + assert.equal(count, 3); + done(); + }); + }); + }); + describe('getSortedSetUnion()', function() { it('should return an array of values from both sorted sets sorted by scores lowest to highest', function(done) { db.getSortedSetUnion({sets: ['sortedSetTest2', 'sortedSetTest3'], start: 0, stop: -1}, function(err, values) { From 7b7ec77f5c7aa8ca3b51d3627eb5aeed623d82cc Mon Sep 17 00:00:00 2001 From: barisusakli Date: Fri, 9 Sep 2016 13:27:19 +0300 Subject: [PATCH 23/28] remove console.log --- src/database/mongo/sorted.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/database/mongo/sorted.js b/src/database/mongo/sorted.js index 237a33deee..fbb727d9c9 100644 --- a/src/database/mongo/sorted.js +++ b/src/database/mongo/sorted.js @@ -489,7 +489,6 @@ module.exports = function(db, module) { pipeline.push({ $project: project }); db.collection('objects').aggregate(pipeline, function(err, data) { - console.log(data); callback(err, Array.isArray(data) && data.length ? data[0].count : 0); }); }; From 7d1bbaa42d15579583179de614c8f25c3bbd37e1 Mon Sep 17 00:00:00 2001 From: NodeBB Misty Date: Sat, 10 Sep 2016 09:02:29 -0400 Subject: [PATCH 24/28] Latest translations and fallbacks --- public/language/pl/user.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/public/language/pl/user.json b/public/language/pl/user.json index d18d541262..302f7b6c7e 100644 --- a/public/language/pl/user.json +++ b/public/language/pl/user.json @@ -119,6 +119,6 @@ "info.no-ban-history": "Ten użytkownik nigdy nie był zbanowany", "info.banned-until": "Zbanowany do %1", "info.banned-permanently": "Zbanowany permanentnie", - "info.banned-reason-label": "Reason", - "info.banned-no-reason": "No reason given." + "info.banned-reason-label": "Powód", + "info.banned-no-reason": "Nie podano powodu." } \ No newline at end of file From 7d46265f29034f5ac036a8b4490c807c7c1ba4f2 Mon Sep 17 00:00:00 2001 From: NodeBB Misty Date: Sun, 11 Sep 2016 09:03:30 -0400 Subject: [PATCH 25/28] Latest translations and fallbacks --- public/language/de/category.json | 4 ++-- public/language/de/error.json | 10 +++++----- public/language/de/topic.json | 6 +++--- public/language/de/user.json | 8 ++++---- public/language/pt_BR/error.json | 8 ++++---- public/language/pt_BR/global.json | 10 +++++----- public/language/pt_BR/user.json | 12 ++++++------ 7 files changed, 29 insertions(+), 29 deletions(-) diff --git a/public/language/de/category.json b/public/language/de/category.json index 9169f01756..f76ea9ae94 100644 --- a/public/language/de/category.json +++ b/public/language/de/category.json @@ -10,8 +10,8 @@ "share_this_category": "Teile diese Kategorie", "watch": "Beobachten", "ignore": "Ignorieren", - "watching": "Watching", - "ignoring": "Ignoring", + "watching": "Beobachte", + "ignoring": "Ignoriere", "watching.description": "Show topics in unread", "ignoring.description": "Do not show topics in unread", "watch.message": "Du beobachtest jetzt Änderungen in dieser Kategorie", diff --git a/public/language/de/error.json b/public/language/de/error.json index 19d65e559a..77cea0ae9e 100644 --- a/public/language/de/error.json +++ b/public/language/de/error.json @@ -20,7 +20,7 @@ "email-taken": "Die E-Mail-Adresse ist bereits vergeben", "email-not-confirmed": "Deine E-Mail wurde noch nicht bestätigt, bitte klicke hier, um deine E-Mail zu bestätigen.", "email-not-confirmed-chat": "Du kannst denn Chat erst nutzen wenn deine E-Mail bestätigt wurde, bitte klicke hier, um deine E-Mail zu bestätigen.", - "email-not-confirmed-email-sent": "Your email has not been confirmed yet, please check your inbox for the confirmation email.", + "email-not-confirmed-email-sent": "Deine E-Mail wurde noch nicht bestätigt, bitte schaue in deinem Posteingang nach der Bestätigungsmail.", "no-email-to-confirm": "Dieses Forum setzt eine E-Mail-Bestätigung voraus, bitte klicke hier um eine E-Mail-Adresse einzugeben.", "email-confirm-failed": "Wir konnten deine E-Mail-Adresse nicht bestätigen, bitte versuch es später noch einmal", "confirm-email-already-sent": "Die Bestätigungsmail wurde verschickt, bitte warte %1 Minute(n) um eine Weitere zu verschicken.", @@ -48,10 +48,10 @@ "post-edit-duration-expired-hours-minutes": "Du darfst Beiträge lediglich innerhalb von %1 Stunde/n und %2 Minute/n nach dem Erstellen editieren", "post-edit-duration-expired-days": "Du darfst Beiträge lediglich innerhalb von %1 Tag/en nach dem Erstellen editieren", "post-edit-duration-expired-days-hours": "Du darfst Beiträge lediglich innerhalb von %1 Tag/en und %2 Stunde/n nach dem Erstellen editieren", - "post-delete-duration-expired": "You are only allowed to delete posts for %1 second(s) after posting", + "post-delete-duration-expired": "Du darfst Beiträge lediglich innerhalb von %1 Sekunden nach dem Erstellen löschen", "post-delete-duration-expired-minutes": "You are only allowed to delete posts for %1 minute(s) after posting", "post-delete-duration-expired-minutes-seconds": "You are only allowed to delete posts for %1 minute(s) %2 second(s) after posting", - "post-delete-duration-expired-hours": "You are only allowed to delete posts for %1 hour(s) after posting", + "post-delete-duration-expired-hours": "Du darfst Beiträge lediglich innerhalb von %1 Stunde/n nach dem Erstellen löschen", "post-delete-duration-expired-hours-minutes": "You are only allowed to delete posts for %1 hour(s) %2 minute(s) after posting", "post-delete-duration-expired-days": "You are only allowed to delete posts for %1 day(s) after posting", "post-delete-duration-expired-days-hours": "You are only allowed to delete posts for %1 day(s) %2 hour(s) after posting", @@ -79,7 +79,7 @@ "invalid-image-extension": "Ungültige Dateinamenerweiterung", "invalid-file-type": "Ungültiger Dateityp. Erlaubte Typen sind: %1", "group-name-too-short": "Gruppenname zu kurz", - "group-name-too-long": "Group name too long", + "group-name-too-long": "Gruppenname zu lang", "group-already-exists": "Gruppe existiert bereits", "group-name-change-not-allowed": "Du kannst den Namen der Gruppe nicht ändern", "group-already-member": "Bereits Teil dieser Gruppe", @@ -122,7 +122,7 @@ "not-in-room": "Benutzer nicht im Raum", "no-users-in-room": "In diesem Raum befinden sich keine Benutzer.", "cant-kick-self": "Du kannst dich nicht selber aus der Gruppe entfernen.", - "no-users-selected": "No user(s) selected", + "no-users-selected": "Keine Benutzer ausgewählt", "invalid-home-page-route": "Invalid home page route", "invalid-session": "Session Mismatch", "invalid-session-text": "It looks like your login session is no longer active, or no longer matches with the server. Please refresh this page." diff --git a/public/language/de/topic.json b/public/language/de/topic.json index 816f417397..e5cbbdc755 100644 --- a/public/language/de/topic.json +++ b/public/language/de/topic.json @@ -27,13 +27,13 @@ "flag": "Markieren", "locked": "Gesperrt", "pinned": "Pinned", - "moved": "Moved", + "moved": "Verschoben", "bookmark_instructions": "Klicke hier, um zum letzten gelesenen Beitrag des Themas zurückzukehren.", "flag_title": "Diesen Beitrag zur Moderation markieren", "flag_success": "Dieser Beitrag wurde erfolgreich für die Moderation markiert.", "deleted_message": "Dieses Thema wurde gelöscht. Nur Nutzer mit entsprechenden Rechten können es sehen.", "following_topic.message": "Du erhälst nun eine Benachrichtigung, wenn jemand einen Beitrag zu diesem Thema verfasst.", - "not_following_topic.message": "You will see this topic in the unread topics list, but you will not receive notifications when somebody posts to this topic.", + "not_following_topic.message": "Ungelesene Beiträge in diesem Thema werden angezeigt, aber du erhältst keine Benachrichtigung wenn jemand einen Beitrag zu diesem Thema verfasst.", "ignoring_topic.message": "Ungelesene Beiträge in diesem Thema werden nicht mehr angezeigt. Du erhältst eine Benachrichtigung wenn du in diesem Thema erwähnt wirst oder deine Beiträge positiv bewertet werden.", "login_to_subscribe": "Bitte registrieren oder einloggen um dieses Thema zu abonnieren", "markAsUnreadForAll.success": "Thema für Alle als ungelesen markiert.", @@ -86,7 +86,7 @@ "topic_will_be_moved_to": "Dieses Thema wird verschoben nach", "fork_topic_instruction": "Klicke auf die Beiträge, die aufgespaltet werden sollen", "fork_no_pids": "Keine Beiträge ausgewählt!", - "fork_pid_count": "%1 post(s) selected", + "fork_pid_count": "%1 Beiträge ausgewählt", "fork_success": "Thema erfolgreich aufgespalten! Klicke hier, um zum aufgespalteten Thema zu gelangen.", "delete_posts_instruction": "Wähle die zu löschenden Beiträge aus", "composer.title_placeholder": "Hier den Titel des Themas eingeben...", diff --git a/public/language/de/user.json b/public/language/de/user.json index aa68a203ba..d8f852f09b 100644 --- a/public/language/de/user.json +++ b/public/language/de/user.json @@ -6,7 +6,7 @@ "postcount": "Beiträge", "email": "E-Mail", "confirm_email": "E-Mail bestätigen", - "account_info": "Account Info", + "account_info": "Kontoinformationen", "ban_account": "Konto sperren", "ban_account_confirm": "Bist du sicher, dass du diesen Benutzer sperren möchtest?", "unban_account": "Konto entsperren", @@ -89,7 +89,7 @@ "topics_per_page": "Themen pro Seite", "posts_per_page": "Beiträge pro Seite", "notification_sounds": "Ton abspielen, wenn du eine Benachrichtigung erhältst", - "notifications_and_sounds": "Notifications & Sounds", + "notifications_and_sounds": "Benachrichtigungen & Klänge", "incoming-message-sound": "Incoming message sound", "outgoing-message-sound": "Outgoing message sound", "notification-sound": "Notification sound", @@ -119,6 +119,6 @@ "info.no-ban-history": "This user has never been banned", "info.banned-until": "Banned until %1", "info.banned-permanently": "Banned permanently", - "info.banned-reason-label": "Reason", - "info.banned-no-reason": "No reason given." + "info.banned-reason-label": "Grund", + "info.banned-no-reason": "Kein Grund angegeben." } \ No newline at end of file diff --git a/public/language/pt_BR/error.json b/public/language/pt_BR/error.json index adec9bbf06..307bc1c6ae 100644 --- a/public/language/pt_BR/error.json +++ b/public/language/pt_BR/error.json @@ -55,8 +55,8 @@ "post-delete-duration-expired-hours-minutes": "Você só pode deletar posts por %1 hora(s) e %2 minutos(s) depois de postar", "post-delete-duration-expired-days": "Você só pode deletar posts por %1 dia(s) depois de postar", "post-delete-duration-expired-days-hours": "Você só pode deletar posts por %1 dia(s) e %2 hora(s) depois de postar", - "cant-delete-topic-has-reply": "You can't delete your topic after it has a reply", - "cant-delete-topic-has-replies": "You can't delete your topic after it has %1 replies", + "cant-delete-topic-has-reply": "Você não pode excluir o seu tópico após ele ter uma resposta", + "cant-delete-topic-has-replies": "Você não pode excluir o seu tópico após ele ter %1 respostas", "content-too-short": "Por favor digite um post maior. Posts precisam conter ao menos %1 caractere(s).", "content-too-long": "Por favor digite um post mais curto. Posts não podem ser maiores que %1 caractere(s)", "title-too-short": "Por favor digite um título maior. Títulos devem conter no mínimo %1 caractere(s)", @@ -124,6 +124,6 @@ "cant-kick-self": "Você não pode kickar a si mesmo do grupo", "no-users-selected": "Nenhuma escolha de usuário(s) foi feita", "invalid-home-page-route": "Rota de página inicial inválida", - "invalid-session": "Session Mismatch", - "invalid-session-text": "It looks like your login session is no longer active, or no longer matches with the server. Please refresh this page." + "invalid-session": "Erro de Sessão", + "invalid-session-text": "Parece que sua sessão de login não está mais ativa, ou não combina mais com a do servidor. Por gentileza, recarregue esta página." } \ No newline at end of file diff --git a/public/language/pt_BR/global.json b/public/language/pt_BR/global.json index 71543c8896..3b9cb20fea 100644 --- a/public/language/pt_BR/global.json +++ b/public/language/pt_BR/global.json @@ -7,10 +7,10 @@ "403.login": "Talvez você deveria tentar fazer login?", "404.title": "Não Encontrado", "404.message": "Parece que você chegou à uma página que não existe. Voltar para a página inicial.", - "500.title": "Internal Error.", + "500.title": "Erro Interno.", "500.message": "Oops! Parece que algo deu errado!", - "400.title": "Bad Request.", - "400.message": "It looks like this link is malformed, please double-check and try again. Otherwise, return to the home page.", + "400.title": "Solicitação Inválida.", + "400.message": "Parece que esse link contém informação inválida, por favor o verifique novamente. Senão, retorne para a página inicial.", "register": "Cadastrar", "login": "Login", "please_log_in": "Por Favor Efetue o Login", @@ -95,6 +95,6 @@ "upload_file": "Fazer upload de arquivo", "upload": "Upload", "allowed-file-types": "Os tipos de arquivo permitidos são %1", - "unsaved-changes": "You have unsaved changes. Are you sure you wish to navigate away?", - "reconnecting-message": "Looks like your connection to %1 was lost, please wait while we try to reconnect." + "unsaved-changes": "Você tem alterações não salvas. Tem certeza que você deseja sair da página?", + "reconnecting-message": "Parece que sua conexão com %1 caiu, por favor aguarde enquanto tentamos reconectar." } \ No newline at end of file diff --git a/public/language/pt_BR/user.json b/public/language/pt_BR/user.json index 1aa9655aea..cc9fd2bd09 100644 --- a/public/language/pt_BR/user.json +++ b/public/language/pt_BR/user.json @@ -89,10 +89,10 @@ "topics_per_page": "Tópicos por Página", "posts_per_page": "Posts por Página", "notification_sounds": "Tocar um som quando você receber uma notificação.", - "notifications_and_sounds": "Notifications & Sounds", - "incoming-message-sound": "Incoming message sound", - "outgoing-message-sound": "Outgoing message sound", - "notification-sound": "Notification sound", + "notifications_and_sounds": "Notificações & Sons", + "incoming-message-sound": "Som de recebimento de mensagem", + "outgoing-message-sound": "Som de envio de mensagem", + "notification-sound": "Som de notificação", "browsing": "Configurações de Navegação", "open_links_in_new_tab": "Abrir links externos em nova aba", "enable_topic_searching": "Habilitar Pesquisa dentro de Tópico", @@ -119,6 +119,6 @@ "info.no-ban-history": "Este usuário nunca foi banido", "info.banned-until": "Banido até %1", "info.banned-permanently": "Banido permanentemente", - "info.banned-reason-label": "Reason", - "info.banned-no-reason": "No reason given." + "info.banned-reason-label": "Motivo", + "info.banned-no-reason": "Sem motivo escolhido." } \ No newline at end of file From 478da29423eaf9607398b1d85c8735d7ea93a879 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Mon, 12 Sep 2016 15:59:38 +0300 Subject: [PATCH 26/28] closes #5020 --- src/middleware/header.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/middleware/header.js b/src/middleware/header.js index 67afe93ed1..26e9795019 100644 --- a/src/middleware/header.js +++ b/src/middleware/header.js @@ -120,6 +120,7 @@ module.exports = function(middleware) { results.user.isAdmin = results.isAdmin; results.user.isGlobalMod = results.isGlobalMod; results.user.uid = parseInt(results.user.uid, 10); + results.user.email = String(results.user.email).replace(/\\/g, '\\\\'); results.user['email:confirmed'] = parseInt(results.user['email:confirmed'], 10) === 1; results.user.isEmailConfirmSent = !!results.isEmailConfirmSent; From ef6e72c2469402d4a6d4f8c4e150fb940d828d25 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Mon, 12 Sep 2016 11:23:19 -0400 Subject: [PATCH 27/28] added test for plugin-included translations, /cc @pitaj --- tests/translator.js | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/tests/translator.js b/tests/translator.js index 7546ec1d90..a86e5b9018 100644 --- a/tests/translator.js +++ b/tests/translator.js @@ -4,6 +4,7 @@ var assert = require('assert'); var translator = require('../public/src/modules/translator.js'); +var plugins = require('../src/plugins'); describe('Translator', function(){ describe('.translate()', function(){ @@ -95,5 +96,18 @@ describe('Translator', function(){ }); }); + it('should properly handle translations added by plugins', function(done) { + plugins.customLanguages = { + 'en_GB/myplugin.json': { + 'foo': 'bar', + 'bar': 'baz, and %1' + } + }; + + translator.translate('[[myplugin:foo]], [[myplugin:bar, quux]]', function(translated) { + assert.strictEqual(translated, 'bar, baz, and quux'); + done(); + }); + }); }); }); From 59ed13b5810b801df61b6c11b48a5d87f3acca08 Mon Sep 17 00:00:00 2001 From: Peter Jaszkowiak Date: Mon, 12 Sep 2016 13:52:50 -0600 Subject: [PATCH 28/28] Rewrite translator for efficiency and clarity (#5013) * Rewrite translator for efficiency and clarity * Fixed failing tests * Gorram tabs * Move cache to Translator class, simplify creation * Documentation and linting * Use new translator API where it makes sense * Revert 499fbe21fdd5b7f2735a27e76a300f69039cfbd3 (except translator.d.ts) * promise polyfill for node<4 * Set `load` on the prototype * Comment on Translator#translate * Delete translator.d.ts * Support translations added by plugins --- package.json | 4 +- public/src/modules/translator.js | 729 +++++++++++++++++-------------- src/meta/js.js | 3 +- tests/translator-new.js | 155 +++++++ tests/translator.js | 5 +- 5 files changed, 563 insertions(+), 333 deletions(-) create mode 100644 tests/translator-new.js diff --git a/package.json b/package.json index 9bd9e7406f..0dfef19c29 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,8 @@ "start": "node loader.js", "lint": "eslint --cache .", "pretest": "npm run lint", - "test": "./node_modules/.bin/istanbul cover ./node_modules/.bin/_mocha -- ./tests -t 10000" + "test": "./node_modules/.bin/istanbul cover ./node_modules/.bin/_mocha -- ./tests -t 10000", + "test-windows": "./node_modules/.bin/_mocha.cmd ./tests -t 10000" }, "dependencies": { "async": "~1.5.0", @@ -69,6 +70,7 @@ "passport": "^0.3.0", "passport-local": "1.0.0", "postcss": "^5.0.13", + "promise-polyfill": "^6.0.2", "prompt": "^1.0.0", "redis": "~2.6.2", "request": "^2.44.0", diff --git a/public/src/modules/translator.js b/public/src/modules/translator.js index 70eaad8d8e..d978abe147 100644 --- a/public/src/modules/translator.js +++ b/public/src/modules/translator.js @@ -1,347 +1,416 @@ -;(function(translator) { - "use strict"; - /* globals RELATIVE_PATH, config, define */ +/* global define, jQuery, config, RELATIVE_PATH, utils, window, Promise, winston */ - var S = null; - var stringDefer = null; - - // export the class if we are in a Node-like system. - if (typeof module === 'object' && module.exports === translator) { - exports = module.exports = translator; - S = require('string'); - } else { - stringDefer = $.Deferred(); - require(['string'], function(stringLib) { - S = stringLib; - stringDefer.resolve(S); - }); +(function (factory) { + 'use strict'; + function loadClient(language, filename) { + return Promise.resolve(jQuery.getJSON(config.relative_path + '/language/' + language + '/' + (filename + '.json?v=' + config['cache-buster']))); } + if (typeof define === 'function' && define.amd) { + // AMD. Register as a named module + define('translator', ['string'], function (string) { + return factory(string, loadClient); + }); + } else if (typeof module === 'object' && module.exports) { + // Node + (function () { + require('promise-polyfill'); + var languages = require('../../../src/languages'); - var languages = {}, - regexes = { - match: /\[\[\w+:[\w\.]+((?!\[\[).)*?\]\]/g, // see tests/translator.js for an explanation re: this monster - split: /[,][\s]*/, - replace: /\]+$/ + function loadServer(language, filename) { + return new Promise(function (resolve, reject) { + languages.get(language, filename + '.json', function (err, data) { + if (err) { + reject(err); + } else { + resolve(data); + } + }); + }); + } + + module.exports = factory(require('string'), loadServer); + })(); + } else { + window.translator = factory(window.string, loadClient); + } +})(function (string, load) { + 'use strict'; + var assign = Object.assign || jQuery.extend; + function classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + + var Translator = function () { + /** + * Construct a new Translator object + * @param {string} language - Language code for this translator instance + */ + function Translator(language) { + classCallCheck(this, Translator); + + if (!language) { + throw new TypeError('Parameter `language` must be a language string. Received ' + language + (language === '' ? '(empty string)' : '')); + } + + this.lang = language; + this.translations = {}; + } + + Translator.prototype.load = load; + + /** + * Parse the translation instructions into the language of the Translator instance + * @param {string} str - Source string + * @returns {Promise} + */ + Translator.prototype.translate = function translate(str) { + // current cursor position + var cursor = 0; + // last break of the input string + var lastBreak = 0; + // length of the input string + var len = str.length; + // array to hold the promises for the translations + // and the strings of untranslated text in between + var toTranslate = []; + + // split a translator string into an array of tokens + // but don't split by commas inside other translator strings + function split(text) { + var len = text.length; + var arr = []; + var i = 0; + var brk = 0; + var level = 0; + + while (i + 2 <= len) { + if (text.slice(i, i + 2) === '[[') { + level += 1; + i += 1; + } else if (text.slice(i, i + 2) === ']]') { + level -= 1; + i += 1; + } else if (level === 0 && text[i] === ',') { + arr.push(text.slice(brk, i).trim()); + i += 1; + brk = i; + } + i += 1; + } + arr.push(text.slice(brk, i + 1).trim()); + return arr; + } + + // the loooop, we'll go to where the cursor + // is equal to the length of the string since + // slice doesn't include the ending index + while (cursor + 2 <= len) { + // if the current position in the string looks + // like the beginning of a translation string + if (str.slice(cursor, cursor + 2) === '[[') { + // split the string from the last break + // to the character before the cursor + // add that to the result array + toTranslate.push(str.slice(lastBreak, cursor)); + // set the cursor position past the beginning + // brackets of the translation string + cursor += 2; + // set the last break to our current + // spot since we just broke the string + lastBreak = cursor; + + // the current level of nesting of the translation strings + var level = 0; + var sliced; + + while (cursor + 2 <= len) { + sliced = str.slice(cursor, cursor + 2); + // if we're at the beginning of another translation string, + // we're nested, so add to our level + if (sliced === '[[') { + level += 1; + cursor += 2; + // if we're at the end of a translation string + } else if (sliced === ']]') { + // if we're at the base level, then this is the end + if (level === 0) { + // so grab the name and args + var result = split(str.slice(lastBreak, cursor)); + var name = result[0]; + var args = result.slice(1); + + // add the translation promise to the array + toTranslate.push(this.translateKey(name, args)); + // skip past the ending brackets + cursor += 2; + // set this as our last break + lastBreak = cursor; + // and we're no longer in a translation string, + // so continue with the main loop + break; + } + // otherwise we lower the level + level -= 1; + // and skip past the ending brackets + cursor += 2; + } else { + // otherwise just move to the next character + cursor += 1; + } + } + } + // move to the next character + cursor += 1; + } + + // add the remaining text after the last translation string + toTranslate.push(str.slice(lastBreak, cursor + 2)); + + // and return a promise for the concatenated translated string + return Promise.all(toTranslate).then(function (translated) { + return translated.join(''); + }); }; - translator.addTranslation = function(language, filename, translations) { - languages[language] = languages[language] || {}; - languages[language].loaded = languages[language].loaded || {}; - languages[language].loading = languages[language].loading || {}; + /** + * Translates a specific key and array of arguments + * @param {string} name - Translation key (ex. 'global:home') + * @param {string[]} args - Arguments for `%1`, `%2`, etc + * @returns {Promise} + */ + Translator.prototype.translateKey = function translateKey(name, args) { + var self = this; - if (languages[language].loaded[filename]) { - for (var t in translations) { - if (translations.hasOwnProperty(t)) { - languages[language].loaded[filename][t] = translations[t]; + var result = name.split(':', 2); + var namespace = result[0]; + var key = result[1]; + + var translation = this.getTranslation(namespace, key); + var argsToTranslate = args.map(function (arg) { + return string(arg).collapseWhitespace().decodeHTMLEntities().escapeHTML().s; + }).map(function (arg) { + return self.translate(arg); + }); + + // so we can await all promises at once + argsToTranslate.unshift(translation); + + return Promise.all(argsToTranslate).then(function (result) { + var translated = result[0]; + var translatedArgs = result.slice(1); + + if (!translated) { + return key; } - } - } else { - languages[language].loaded[filename] = translations; - } - }; - - translator.getTranslations = function(language, filename, callback) { - if (languages[language] && languages[language].loaded[filename]) { - callback(languages[language].loaded[filename]); - } else { - translator.load(language, filename, function() { - callback(languages[language].loaded[filename]); + var out = translated; + translatedArgs.forEach(function (arg, i) { + out = out.replace(new RegExp('%' + (i + 1), 'g'), arg); + }); + return out; }); - } - }; + }; - translator.escape = function(text) { - return typeof text === 'string' ? text.replace(/\[\[([\S]*?)\]\]/g, '\\[\\[$1\\]\\]') : text; - }; - - translator.unescape = function(text) { - return typeof text === 'string' ? text.replace(/\\\[\\\[([\S]*?)\\\]\\\]/g, '[[$1]]') : text; - }; - - translator.getLanguage = function() { - return config.defaultLang; - }; - - translator.prepareDOM = function() { - // Load the appropriate timeago locale file, and correct NodeBB language codes to timeago codes, if necessary - var languageCode; - switch(config.userLang) { - case 'en_GB': - case 'en_US': - languageCode = 'en'; - break; - - case 'fa_IR': - languageCode = 'fa'; - break; - - case 'pt_BR': - languageCode = 'pt-br'; - break; - - case 'nb': - languageCode = 'no'; - break; - - case 'zh_TW': - languageCode = 'zh-TW'; - break; - - case 'zh_CN': - languageCode = 'zh-CN'; - break; - - default: - languageCode = config.userLang; - break; - } - - $.getScript(RELATIVE_PATH + '/vendor/jquery/timeago/locales/jquery.timeago.' + languageCode + '.js').done(function() { - $('.timeago').timeago(); - translator.timeagoShort = $.extend({}, jQuery.timeago.settings.strings); - - // Retrieve the shorthand timeago values as well - $.getScript(RELATIVE_PATH + '/vendor/jquery/timeago/locales/jquery.timeago.' + languageCode + '-short.js').done(function() { - // Switch back to long-form - translator.toggleTimeagoShorthand(); - }); - }); - - // Add directional code if necessary - translator.translate('[[language:dir]]', function(value) { - if (value) { - $('html').css('direction', value).attr('data-dir', value); + /** + * Load translation file (or use a cached version), and optionally return the translation of a certain key + * @param {string} namespace - The file name of the translation namespace + * @param {string} [key] - The key of the specific translation to getJSON + * @returns {Promise} + */ + Translator.prototype.getTranslation = function getTranslation(namespace, key) { + var translation; + if (!namespace) { + winston.warn('[translator] Parameter `namespace` is ' + namespace + (namespace === '' ? '(empty string)' : '')); + translation = Promise.resolve({}); + } else if (this.translations[namespace]) { + translation = this.translations[namespace]; + } else { + translation = this.load(this.lang, namespace); + this.translations[namespace] = translation; } - }); - }; - translator.toggleTimeagoShorthand = function() { - var tmp = $.extend({}, jQuery.timeago.settings.strings); - jQuery.timeago.settings.strings = $.extend({}, translator.timeagoShort); - translator.timeagoShort = $.extend({}, tmp); - }; + if (key) { + return translation.then(function (x) { + return x[key]; + }); + } + return translation; + }; - translator.translate = function (text, language, callback) { - if (typeof language === 'function') { - callback = language; - if ('undefined' !== typeof window && config) { - language = utils.params().lang || config.userLang || 'en_GB'; + /** + * Get the language of the current environment, falling back to defaults + * @returns {string} + */ + Translator.getLanguage = function getLanguage() { + var lang; + + if (typeof window === 'object' && window.config && window.utils) { + lang = utils.params().lang || config.userLang || config.defaultLang || 'en_GB'; } else { var meta = require('../../../src/meta'); - language = meta.config.defaultLang || 'en_GB'; - } - } - - if (!text) { - return callback(text); - } - - var keys = text.match(regexes.match); - - if (!keys) { - return callback(text); - } - - translateKeys(keys, text, language, function(translated) { - keys = translated.match(regexes.match); - if (!keys) { - callback(translated); - } else { - translateKeys(keys, translated, language, callback); - } - }); - }; - - function translateKeys(keys, text, language, callback) { - - var count = keys.length; - if (!count) { - return callback(text); - } - - if (S === null) { // browser environment and S not yet initialized - // we need to wait for async require call - stringDefer.then(function () { translateKeys(keys, text, language, callback); }); - return; - } - - var data = {text: text}; - keys.forEach(function(key) { - translateKey(key, data, language, function(translated) { - --count; - if (count <= 0) { - callback(translated.text); - } - }); - }); - } - - function translateKey(key, data, language, callback) { - key = '' + key; - var variables = key.split(regexes.split); - - var parsedKey = key.replace('[[', '').replace(']]', '').split(':'); - parsedKey = [parsedKey[0]].concat(parsedKey.slice(1).join(':')); - if (!(parsedKey[0] && parsedKey[1])) { - return callback(data); - } - - var languageFile = parsedKey[0]; - parsedKey = ('' + parsedKey[1]).split(',')[0]; - - translator.load(language, languageFile, function(languageData) { - data.text = insertLanguage(data.text, key, languageData[parsedKey], variables); - callback(data); - }); - } - - function insertLanguage(text, key, value, variables) { - if (value) { - variables.forEach(function(variable, index) { - if (index > 0) { - variable = S(variable).chompRight(']]').collapseWhitespace().decodeHTMLEntities().escapeHTML().s; - value = value.replace('%' + index, function() { return variable; }); - } - }); - - text = text.replace(key, function() { return value; }); - } else { - var string = key.split(':'); - text = text.replace(key, string[string.length-1].replace(regexes.replace, '')); - } - - return text; - } - - translator.compile = function() { - var args = Array.prototype.slice.call(arguments, 0); - - return '[[' + args.join(', ') + ']]'; - }; - - translator.load = function (language, filename, callback) { - if (isLanguageFileLoaded(language, filename)) { - if (callback) { - callback(languages[language].loaded[filename]); - } - } else if (isLanguageFileLoading(language, filename)) { - if (callback) { - addLanguageFileCallback(language, filename, callback); - } - } else { - - languages[language] = languages[language] || {loading: {}, loaded: {}, callbacks: []}; - - languages[language].loading[filename] = true; - - load(language, filename, function(translations) { - - languages[language].loaded[filename] = translations; - - if (callback) { - callback(translations); - } - - while (languages[language].callbacks && languages[language].callbacks[filename] && languages[language].callbacks[filename].length) { - languages[language].callbacks[filename].pop()(translations); - } - - languages[language].loading[filename] = false; - }); - } - }; - - function isLanguageFileLoaded(language, filename) { - var languageObj = languages[language]; - return languageObj && languageObj.loaded && languageObj.loaded[filename] && !languageObj.loading[filename]; - } - - function isLanguageFileLoading(language, filename) { - return languages[language] && languages[language].loading && languages[language].loading[filename]; - } - - function addLanguageFileCallback(language, filename, callback) { - languages[language].callbacks = languages[language].callbacks || {}; - - languages[language].callbacks[filename] = languages[language].callbacks[filename] || []; - languages[language].callbacks[filename].push(callback); - } - - function load(language, filename, callback) { - if ('undefined' !== typeof window) { - loadClient(language, filename, callback); - } else { - loadServer(language, filename, callback); - } - } - - function loadClient(language, filename, callback) { - $.getJSON(config.relative_path + '/language/' + language + '/' + filename + '.json?v=' + config['cache-buster'], callback); - } - - function loadServer(language, filename, callback) { - var fs = require('fs'), - path = require('path'), - winston = require('winston'), - _ = require('underscore'), - file = require('../../../src/file'), - plugins = require('../../../src/plugins'), - meta = require('../../../src/meta'); - - var hash = language + '/' + filename + '.json'; - - language = language || meta.config.defaultLang || 'en_GB'; - - if (!file.existsSync(path.join(__dirname, '../../language', language))) { - winston.warn('[translator] Language \'' + meta.config.defaultLang + '\' not found. Defaulting to \'en_GB\''); - language = 'en_GB'; - } - - fs.readFile(path.join(__dirname, '../../language', hash), function(err, data) { - var onData = function(data) { - try { - data = JSON.parse(data.toString()); - - if (plugins.customLanguages.hasOwnProperty(hash)) { - _.extendOwn(data, plugins.customLanguages[hash]); - } - } catch (e) { - winston.error('Could not parse `' + filename + '.json`, syntax error? Skipping...'); - data = {}; - } - - callback(data); - }; - - if (err && err.code === 'ENOENT') { - data = '{}'; - } else if (err) { - winston.error('[translator] Error while loading language file: ' + err.message); - return callback({}); + lang = meta.config.defaultLang || 'en_GB'; } - onData(data); - }); - } - - // Use the define() function if we're in AMD land - if (typeof define === 'function' && define.amd) { - define('translator', translator); - - var _translator = translator; - - // Expose a global `translator` object for backwards compatibility - window.translator = { - translate: function() { - if (typeof console !== 'undefined' && console.warn) { - console.warn('[translator] Global invocation of the translator is now deprecated, please `require` the module instead.'); - } - _translator.translate.apply(_translator, arguments); - } + return lang; }; - } -})( - typeof exports === 'object' ? exports : - typeof define === 'function' && define.amd ? {} : - translator = {} -); + + /** + * Create and cache a new Translator instance, or return a cached one + * @param {string} [language] - ('en_GB') Language string + * @returns {Translator} + */ + Translator.create = function create(language) { + if (!language) { + language = Translator.getLanguage(); + } + + Translator.cache[language] = Translator.cache[language] || new Translator(language); + + return Translator.cache[language]; + }; + + Translator.cache = {}; + + return Translator; + }(); + + var adaptor = { + /** + * The Translator class + */ + Translator: Translator, + + /** + * Legacy translator function for backwards compatibility + */ + translate: function translate(text, language, callback) { + // console.warn('[translator] `translator.translate(text, [lang, ]callback)` is deprecated. ' + + // 'Use the `translator.Translator` class instead.'); + + var cb = callback; + var lang = language; + if (typeof language === 'function') { + cb = language; + lang = null; + } + + Translator.create(lang).translate(text).then(function (output) { + return cb(output); + }).catch(function (err) { + console.error('Translation failed: ' + err.message); + }); + }, + + /** + * Construct a translator pattern + * @param {string} name - Translation name + * @param {string[]} args - Optional arguments for the pattern + */ + compile: function compile() { + var args = Array.prototype.slice.call(arguments, 0); + + return '[[' + args.join(', ') + ']]'; + }, + + /** + * Escape translation patterns from text + */ + escape: function escape(text) { + return typeof text === 'string' ? text.replace(/\[\[([\S]*?)\]\]/g, '\\[\\[$1\\]\\]') : text; + }, + + /** + * Unescape translation patterns from text + */ + unescape: function unescape(text) { + return typeof text === 'string' ? text.replace(/\\\[\\\[([\S]*?)\\\]\\\]/g, '[[$1]]') : text; + }, + + /** + * Add translations to the cache + */ + addTranslation: function addTranslation(language, filename, translation) { + Translator.create(language).getTranslation(filename).then(function (translations) { + assign(translations, translation); + }); + }, + + /** + * Get the translations object + */ + getTranslations: function getTranslations(language, filename, callback) { + callback = callback || function () {}; + Translator.create(language).getTranslation(filename).then(callback); + }, + + /** + * Alias of getTranslations + */ + load: function load(language, filename, callback) { + adaptor.getTranslations(language, filename, callback); + }, + + /** + * Get the language of the current environment, falling back to defaults + */ + getLanguage: Translator.getLanguage, + + toggleTimeagoShorthand: function toggleTimeagoShorthand() { + var tmp = assign({}, jQuery.timeago.settings.strings); + jQuery.timeago.settings.strings = assign({}, adaptor.timeagoShort); + adaptor.timeagoShort = assign({}, tmp); + }, + prepareDOM: function prepareDOM() { + // Load the appropriate timeago locale file, + // and correct NodeBB language codes to timeago codes, if necessary + var languageCode = void 0; + switch (config.userLang) { + case 'en_GB': + case 'en_US': + languageCode = 'en'; + break; + + case 'fa_IR': + languageCode = 'fa'; + break; + + case 'pt_BR': + languageCode = 'pt-br'; + break; + + case 'nb': + languageCode = 'no'; + break; + + case 'zh_TW': + languageCode = 'zh-TW'; + break; + + case 'zh_CN': + languageCode = 'zh-CN'; + break; + + default: + languageCode = config.userLang; + break; + } + + jQuery.getScript(RELATIVE_PATH + '/vendor/jquery/timeago/locales/jquery.timeago.' + languageCode + '.js').done(function () { + jQuery('.timeago').timeago(); + adaptor.timeagoShort = assign({}, jQuery.timeago.settings.strings); + + // Retrieve the shorthand timeago values as well + jQuery.getScript(RELATIVE_PATH + '/vendor/jquery/timeago/locales/jquery.timeago.' + languageCode + '-short.js').done(function () { + // Switch back to long-form + adaptor.toggleTimeagoShorthand(); + }); + }); + + // Add directional code if necessary + adaptor.translate('[[language:dir]]', function (value) { + if (value) { + jQuery('html').css('direction', value).attr('data-dir', value); + } + }); + } + }; + + return adaptor; +}); diff --git a/src/meta/js.js b/src/meta/js.js index 79377826ba..e729937559 100644 --- a/src/meta/js.js +++ b/src/meta/js.js @@ -37,7 +37,8 @@ module.exports = function(Meta) { 'public/src/app.js', 'public/src/ajaxify.js', 'public/src/overrides.js', - 'public/src/widgets.js' + 'public/src/widgets.js', + "./node_modules/promise-polyfill/promise.js" ], // files listed below are only available client-side, or are bundled in to reduce # of network requests on cold load diff --git a/tests/translator-new.js b/tests/translator-new.js new file mode 100644 index 0000000000..22ddf4e9d5 --- /dev/null +++ b/tests/translator-new.js @@ -0,0 +1,155 @@ +'use strict'; +/*global require*/ + +var assert = require('assert'); +var Translator = require('../public/src/modules/translator.js').Translator; + + +describe('new Translator(language)', function(){ + describe('.translate()', function(){ + it('should handle basic translations', function(done) { + var translator = new Translator('en_GB'); + + translator.translate('[[global:home]]').then(function(translated) { + assert.strictEqual(translated, 'Home'); + done(); + }); + }); + + it('should handle language keys in regular text', function(done) { + var translator = new Translator('en_GB'); + + translator.translate('Let\'s go [[global:home]]').then(function(translated) { + assert.strictEqual(translated, 'Let\'s go Home'); + done(); + }); + }); + + it('should accept a language parameter and adjust accordingly', function(done) { + var translator = new Translator('de'); + + translator.translate('[[global:home]]').then(function(translated) { + assert.strictEqual(translated, 'Übersicht'); + done(); + }); + }); + + it('should handle language keys in regular text with another language specified', function(done) { + var translator = new Translator('de'); + + translator.translate('[[global:home]] test').then(function(translated) { + assert.strictEqual(translated, 'Übersicht test'); + done(); + }); + }); + + it('should handle language keys with parameters', function(done) { + var translator = new Translator('en_GB'); + + translator.translate('[[global:pagination.out_of, 1, 5]]').then(function(translated) { + assert.strictEqual(translated, '1 out of 5'); + done(); + }); + }); + + it('should handle language keys inside language keys', function(done) { + var translator = new Translator('en_GB'); + + translator.translate('[[notifications:outgoing_link_message, [[global:guest]]]]').then(function(translated) { + assert.strictEqual(translated, 'You are now leaving Guest'); + done(); + }); + }); + + it('should handle language keys inside language keys with multiple parameters', function(done) { + var translator = new Translator('en_GB'); + + translator.translate('[[notifications:user_posted_to, [[global:guest]], My Topic]]').then(function(translated) { + assert.strictEqual(translated, 'Guest has posted a reply to: My Topic'); + done(); + }); + }); + + it('should handle language keys inside language keys with all parameters as language keys', function(done) { + var translator = new Translator('en_GB'); + + translator.translate('[[notifications:user_posted_to, [[global:guest]], [[global:guest]]]]').then(function(translated) { + assert.strictEqual(translated, 'Guest has posted a reply to: Guest'); + done(); + }); + }); + + it('should properly handle parameters that contain square brackets', function(done) { + var translator = new Translator('en_GB'); + + translator.translate('[[global:pagination.out_of, [guest], [[global:home]]]]').then(function(translated) { + assert.strictEqual(translated, '[guest] out of Home'); + done(); + }); + }); + + it('should properly handle parameters that contain parentheses', function(done) { + var translator = new Translator('en_GB'); + + translator.translate('[[global:pagination.out_of, (foobar), [[global:home]]]]').then(function(translated) { + assert.strictEqual(translated, '(foobar) out of Home'); + done(); + }); + }); + + it('should not translate language key parameters with HTML in them', function(done) { + var translator = new Translator('en_GB'); + + var key = '[[global:403.login, test]]'; + translator.translate(key).then(function(translated) { + assert.strictEqual(translated, 'Perhaps you should try logging in?'); + done(); + }); + }); + + it('should properly escape % and ,', function(done) { + var translator = new Translator('en_GB'); + + var title = 'Test 1, 2, 3 % salmon'; + title = title.replace(/%/g, '%').replace(/,/g, ','); + var key = "[[topic:composer.replying_to, " + title + "]]"; + translator.translate(key).then(function(translated) { + assert.strictEqual(translated, 'Replying to Test 1, 2, 3 % salmon'); + done(); + }); + }); + + it('should throw if not passed a language', function(done) { + assert.throws(function () { + new Translator(); + }, /language string/); + done(); + }); + + }); +}); + +describe('Translator.create()', function(){ + describe('.translate()', function(){ + it('should return an instance of Translator', function(done) { + var translator = Translator.create('en_GB'); + + assert(translator instanceof Translator); + done(); + }); + it('should return the same object for the same language', function(done) { + var one = Translator.create('de'); + var two = Translator.create('de'); + + assert.strictEqual(one, two); + done(); + }); + it('should default to defaultLang', function(done) { + var translator = Translator.create(); + + assert.strictEqual(translator.lang, 'en_GB'); + done(); + }); + + }); +}); diff --git a/tests/translator.js b/tests/translator.js index a86e5b9018..f1ceeaed0d 100644 --- a/tests/translator.js +++ b/tests/translator.js @@ -5,8 +5,11 @@ var assert = require('assert'); var translator = require('../public/src/modules/translator.js'); var plugins = require('../src/plugins'); +var languages = require('../src/languages'); -describe('Translator', function(){ +languages.init(function(){}); + +describe('translator adaptor', function(){ describe('.translate()', function(){ it('should handle basic translations', function(done) { translator.translate('[[global:home]]', function(translated) {