mirror of
https://github.com/NodeBB/NodeBB.git
synced 2026-01-06 15:42:52 +01:00
Compare commits
31 Commits
v1.1.3-aut
...
v1.1.3-aut
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0162514e8d | ||
|
|
2d3e7589ad | ||
|
|
59ed13b581 | ||
|
|
ef6e72c246 | ||
|
|
478da29423 | ||
|
|
7d46265f29 | ||
|
|
7d1bbaa42d | ||
|
|
7b7ec77f5c | ||
|
|
e1263f1057 | ||
|
|
77c549081c | ||
|
|
7f27a21441 | ||
|
|
650d74e28b | ||
|
|
eabb745116 | ||
|
|
8d897d8dcf | ||
|
|
fdf2cb6f81 | ||
|
|
22ab41f998 | ||
|
|
d0a94cb86c | ||
|
|
b2aaeaa50d | ||
|
|
bba3c0623e | ||
|
|
8273dab368 | ||
|
|
d4c512cb3f | ||
|
|
3c0a28d293 | ||
|
|
7269d45e84 | ||
|
|
46d2168918 | ||
|
|
24e0c9f6fe | ||
|
|
3cf4449020 | ||
|
|
1b7aa5ac12 | ||
|
|
3cdfd22a65 | ||
|
|
a578840904 | ||
|
|
4410385e8f | ||
|
|
00a06e9253 |
12
package.json
12
package.json
@@ -2,7 +2,7 @@
|
||||
"name": "nodebb",
|
||||
"license": "GPL-3.0",
|
||||
"description": "NodeBB Forum",
|
||||
"version": "1.1.3-auto.3",
|
||||
"version": "1.1.3-auto.4",
|
||||
"homepage": "http://www.nodebb.org",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@@ -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",
|
||||
@@ -50,7 +51,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.4",
|
||||
"nodebb-plugin-dbsearch": "1.0.2",
|
||||
"nodebb-plugin-emoji-extended": "1.1.1",
|
||||
"nodebb-plugin-emoji-one": "1.1.5",
|
||||
@@ -60,8 +61,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.25",
|
||||
"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",
|
||||
@@ -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",
|
||||
|
||||
@@ -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."
|
||||
}
|
||||
@@ -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": "Няма посочена причина."
|
||||
}
|
||||
@@ -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."
|
||||
}
|
||||
@@ -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."
|
||||
}
|
||||
@@ -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."
|
||||
}
|
||||
@@ -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",
|
||||
|
||||
@@ -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."
|
||||
|
||||
@@ -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...",
|
||||
|
||||
@@ -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",
|
||||
@@ -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": "Grund",
|
||||
"info.banned-no-reason": "Kein Grund angegeben."
|
||||
}
|
||||
@@ -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."
|
||||
}
|
||||
@@ -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."
|
||||
}
|
||||
@@ -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."
|
||||
}
|
||||
@@ -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).",
|
||||
@@ -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."
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
@@ -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."
|
||||
}
|
||||
@@ -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": "آیا مطمئنید که میخواهید حساب کاربری خود را حذف کنید؟ <br /><strong>این عمل غیر قابل بازگشت است و شما قادر نخواهید بود هیچ کدام از اطلاعات خود را بازیابی کنید./strong><br /><br /> برای تایید حذف این حساب کاربری، نام کاربری خود را وارد کنید",
|
||||
"delete_this_account_confirm": "آیا مطمئنید که میخواهید این حساب کاربری را حذف کنید؟<br/><strong>این عمل غیر قابل بازگشت است و شما قادر نخواهید بود هیچ کدام از اطلاعات را بازیابی کنید.</strong><br/><br/>",
|
||||
"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": "هفتگی",
|
||||
@@ -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": "هیچ دلیلی ارایه نشد."
|
||||
}
|
||||
@@ -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": "کاربری پیدا نشد!"
|
||||
}
|
||||
@@ -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."
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
@@ -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."
|
||||
}
|
||||
@@ -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."
|
||||
}
|
||||
@@ -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."
|
||||
}
|
||||
@@ -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."
|
||||
}
|
||||
@@ -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."
|
||||
}
|
||||
@@ -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."
|
||||
}
|
||||
@@ -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."
|
||||
}
|
||||
@@ -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": "<strong>Šioje kategorijoje temų nėra.</strong><br/>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"
|
||||
}
|
||||
@@ -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",
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
@@ -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ą? <br /> <strong> Šis veiksmas yra negrįžtamas, ir jūs negalėsite susigrąžinti jokių duomenų </ strong> <br /> <br /> Įveskite savo vardą, kad patvirtintumėte, jog norite panaikinti šią paskyrą.",
|
||||
"delete_this_account_confirm": "Ar jūs tikrai norite ištrint šią paskyrą? <br /><strong>Šis veiksmas nebesugražinamas ir jūs nebegalėsite atgauti jokių duomenų</strong><br /><br />",
|
||||
"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 <strong>%1</strong>",
|
||||
"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,43 +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.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"
|
||||
}
|
||||
@@ -17,5 +17,5 @@
|
||||
"unread_topics": "Neperskaitytos temos",
|
||||
"categories": "Kategorijos",
|
||||
"tags": "Žymos",
|
||||
"no-users-found": "No users found!"
|
||||
"no-users-found": "Nerasta vartotojų."
|
||||
}
|
||||
@@ -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."
|
||||
}
|
||||
@@ -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."
|
||||
}
|
||||
@@ -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."
|
||||
}
|
||||
@@ -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": "Powód",
|
||||
"info.banned-no-reason": "Nie podano powodu."
|
||||
}
|
||||
@@ -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."
|
||||
}
|
||||
@@ -7,10 +7,10 @@
|
||||
"403.login": "Talvez você deveria <a href='%1/login'>tentar fazer login</a>?",
|
||||
"404.title": "Não Encontrado",
|
||||
"404.message": "Parece que você chegou à uma página que não existe. Voltar para a <a href='%1/'>página inicial</a>.",
|
||||
"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 <a href='%1/'>home page</a>.",
|
||||
"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 <a href='%1/'>página inicial</a>.",
|
||||
"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."
|
||||
}
|
||||
@@ -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",
|
||||
@@ -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": "Motivo",
|
||||
"info.banned-no-reason": "Sem motivo escolhido."
|
||||
}
|
||||
@@ -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."
|
||||
}
|
||||
@@ -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."
|
||||
}
|
||||
@@ -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."
|
||||
}
|
||||
@@ -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."
|
||||
}
|
||||
@@ -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."
|
||||
}
|
||||
@@ -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."
|
||||
}
|
||||
@@ -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."
|
||||
}
|
||||
@@ -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."
|
||||
}
|
||||
@@ -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."
|
||||
}
|
||||
@@ -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."
|
||||
}
|
||||
@@ -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."
|
||||
}
|
||||
@@ -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."
|
||||
}
|
||||
@@ -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."
|
||||
}
|
||||
@@ -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": "看起來你的登入會話階段已經無效,或是不符合於伺服器。請重新整理這個頁面。"
|
||||
}
|
||||
@@ -11,7 +11,7 @@
|
||||
"pending.none": "目前沒有等待中的會員",
|
||||
"invited.none": "目前沒有邀請的會員",
|
||||
"invited.uninvite": "撤銷邀請",
|
||||
"invited.search": "搜尋要邀請加入這個群組的用戶",
|
||||
"invited.search": "搜尋要邀請加入這個群組的使用者",
|
||||
"invited.notification_title": "你已被邀請加入<strong>%1</strong>",
|
||||
"request.notification_title": "群組會員要求,來自<strong>%1</strong>",
|
||||
"request.notification_text": "<strong>%1</strong>已經要求成為<strong>%2</strong>群組的會員",
|
||||
@@ -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": "群組詳細訊息已被更新",
|
||||
|
||||
@@ -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": "隱藏預覽",
|
||||
|
||||
@@ -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 所負向投票的張貼",
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
{
|
||||
"register": "註冊",
|
||||
"cancel_registration": "取消註冊",
|
||||
"help.email": "默認情況下,你的郵箱不會公開。",
|
||||
"help.email": "在預設情況下,你的電子郵件地址不會被公開。",
|
||||
"help.username_restrictions": "獨立的帳號由 %1 到 %2 個字元組成。其他人可以通過 @<span id='yourUsername'>帳號</span> 提及你。",
|
||||
"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": "確認密碼",
|
||||
|
||||
@@ -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": "排序方式",
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
"ban_account_confirm": "你確定要禁用這個使用者?",
|
||||
"unban_account": "取消禁用帳號",
|
||||
"delete_account": "刪除帳戶",
|
||||
"delete_account_confirm": "你確定要刪除自己的帳戶? <br /><strong>此操作不能復原,你將無法恢復任何數據</strong><br /><br />輸入你的帳號,用來確認希望刪除這個帳戶。",
|
||||
"delete_account_confirm": "你確定要刪除自己的帳戶? <br /><strong>此操作不能復原,你將無法恢復任何資料</strong><br /><br />輸入你的帳號,來確認你希望刪除這個帳戶。",
|
||||
"delete_this_account_confirm": "你確定要刪除這個帳戶? <br /><strong>此操作是不能還原的,你將無法回復任何資料</strong><br />",
|
||||
"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": "每頁的主題數",
|
||||
@@ -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": "沒有給理由"
|
||||
}
|
||||
@@ -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": "沒有找到使用者!"
|
||||
}
|
||||
@@ -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] : [])
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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<string>}
|
||||
*/
|
||||
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<string>}
|
||||
*/
|
||||
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<Object|string>}
|
||||
*/
|
||||
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;
|
||||
});
|
||||
|
||||
@@ -474,48 +474,82 @@ module.exports = function(db, module) {
|
||||
});
|
||||
};
|
||||
|
||||
module.getSortedSetUnion = function(sets, start, stop, callback) {
|
||||
getSortedSetUnion(sets, 1, start, stop, callback);
|
||||
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) {
|
||||
callback(err, Array.isArray(data) && data.length ? data[0].count : 0);
|
||||
});
|
||||
};
|
||||
|
||||
module.getSortedSetRevUnion = 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(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);
|
||||
});
|
||||
}
|
||||
@@ -613,7 +647,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;
|
||||
|
||||
@@ -232,32 +232,51 @@ module.exports = function(redisClient, module) {
|
||||
multi.exec(callback);
|
||||
};
|
||||
|
||||
module.getSortedSetUnion = function(sets, start, stop, callback) {
|
||||
sortedSetUnion('zrange', sets, start, stop, false, callback);
|
||||
};
|
||||
|
||||
module.getSortedSetRevUnion = function(sets, start, stop, callback) {
|
||||
sortedSetUnion('zrevrange', sets, start, stop, false, callback);
|
||||
};
|
||||
|
||||
function sortedSetUnion(method, sets, start, stop, withScores, callback) {
|
||||
|
||||
module.sortedSetUnionCard = function(keys, callback) {
|
||||
var tempSetName = 'temp_' + Date.now();
|
||||
|
||||
var params = [tempSetName, start, stop];
|
||||
if (withScores) {
|
||||
params.push('WITHSCORES');
|
||||
}
|
||||
|
||||
var multi = redisClient.multi();
|
||||
multi.zunionstore([tempSetName, sets.length].concat(sets));
|
||||
multi[method](params);
|
||||
multi.zunionstore([tempSetName, keys.length].concat(keys));
|
||||
multi.zcard(tempSetName);
|
||||
multi.del(tempSetName);
|
||||
multi.exec(function(err, results) {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
if (!withScores) {
|
||||
|
||||
callback(null, Array.isArray(results) && results.length ? results[1] : 0);
|
||||
});
|
||||
};
|
||||
|
||||
module.getSortedSetUnion = function(params, callback) {
|
||||
params.method = 'zrange';
|
||||
sortedSetUnion(params, callback);
|
||||
};
|
||||
|
||||
module.getSortedSetRevUnion = function(params, callback) {
|
||||
params.method = 'zrevrange';
|
||||
sortedSetUnion(params, callback);
|
||||
};
|
||||
|
||||
function sortedSetUnion(params, callback) {
|
||||
|
||||
var tempSetName = 'temp_' + Date.now();
|
||||
|
||||
var rangeParams = [tempSetName, params.start, params.stop];
|
||||
if (params.withScores) {
|
||||
params.push('WITHSCORES');
|
||||
}
|
||||
|
||||
var multi = redisClient.multi();
|
||||
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 (!params.withScores) {
|
||||
return callback(null, results ? results[1] : null);
|
||||
}
|
||||
results = results[1] || [];
|
||||
|
||||
@@ -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.each(Object.keys(defaults), function (key, next) {
|
||||
meta.configs.setOnEmpty(key, defaults[key], next);
|
||||
}, function (err) {
|
||||
meta.configs.setOnEmpty(defaults, function (err) {
|
||||
if (err) {
|
||||
return next(err);
|
||||
}
|
||||
|
||||
@@ -118,14 +118,20 @@ 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);
|
||||
data = data || {};
|
||||
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();
|
||||
}
|
||||
|
||||
@@ -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
|
||||
@@ -79,7 +80,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'
|
||||
}
|
||||
|
||||
@@ -38,14 +38,21 @@ 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);
|
||||
}
|
||||
settings = settings || {};
|
||||
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();
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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<numPaths;x++) {
|
||||
delete require.cache[data.paths[x]];
|
||||
}
|
||||
winston.verbose('[plugins] Plugin libraries removed from Node.js cache');
|
||||
|
||||
next();
|
||||
}
|
||||
], next);
|
||||
};
|
||||
|
||||
}(exports));
|
||||
|
||||
@@ -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]]'));
|
||||
|
||||
@@ -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);
|
||||
};
|
||||
|
||||
@@ -263,33 +263,75 @@ 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);
|
||||
});
|
||||
}
|
||||
|
||||
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(null, []);
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
data.query = data.query.toLowerCase();
|
||||
query = query.toLowerCase();
|
||||
|
||||
var matches = [];
|
||||
for(var i=0; i<tags.length; ++i) {
|
||||
if (tags[i].toLowerCase().startsWith(data.query)) {
|
||||
if (tags[i].toLowerCase().startsWith(query)) {
|
||||
matches.push(tags[i]);
|
||||
if (matches.length > 19) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
matches = matches.slice(0, 20).sort(function(a, b) {
|
||||
matches = matches.sort(function(a, b) {
|
||||
return a > b;
|
||||
});
|
||||
|
||||
plugins.fireHook('filter:tags.search', {data: data, matches: matches}, function(err, data) {
|
||||
callback(err, data ? data.matches : []);
|
||||
});
|
||||
callback(null, matches);
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
Topics.searchAndLoadTags = function(data, callback) {
|
||||
var searchResult = {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -22,6 +22,12 @@
|
||||
<label>Maximum number of users in chat rooms</label>
|
||||
<input type="text" class="form-control" value="0" data-field="maximumUsersInChatRoom">
|
||||
</div>
|
||||
|
||||
|
||||
<div class="form-group">
|
||||
<label>Time between chat messages in milliseconds</label>
|
||||
<input type="text" class="form-control" value="200" data-field="chatMessageDelay">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -436,9 +436,19 @@ 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(['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 +459,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']);
|
||||
|
||||
155
tests/translator-new.js
Normal file
155
tests/translator-new.js
Normal file
@@ -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, '<strong>Guest</strong> has posted a reply to: <strong>My Topic</strong>');
|
||||
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, '<strong>Guest</strong> has posted a reply to: <strong>Guest</strong>');
|
||||
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, <strong>test</strong>]]';
|
||||
translator.translate(key).then(function(translated) {
|
||||
assert.strictEqual(translated, 'Perhaps you should <a href=\'<strong>test</strong>/login\'>try logging in</a>?');
|
||||
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();
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
@@ -4,8 +4,12 @@
|
||||
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) {
|
||||
@@ -95,5 +99,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();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user