diff --git a/.gitignore b/.gitignore index 24022b9d7..aaf49c933 100644 --- a/.gitignore +++ b/.gitignore @@ -45,6 +45,7 @@ plugins/* !plugins/talk-plugin-moderation-actions !plugins/talk-plugin-notifications !plugins/talk-plugin-notifications-category-featured +!plugins/talk-plugin-notifications-category-moderation-actions !plugins/talk-plugin-notifications-category-reply !plugins/talk-plugin-notifications-category-staff !plugins/talk-plugin-notifications-digest-daily diff --git a/Dockerfile.onbuild b/Dockerfile.onbuild index ac88bd509..3b05ec90b 100644 --- a/Dockerfile.onbuild +++ b/Dockerfile.onbuild @@ -4,6 +4,7 @@ FROM coralproject/talk:latest ONBUILD ARG TALK_ADDTL_COMMENTS_ON_LOAD_MORE=10 ONBUILD ARG TALK_ASSET_COMMENTS_LOAD_DEPTH=10 ONBUILD ARG TALK_REPLY_COMMENTS_LOAD_DEPTH=3 +ONBUILD ARG TALK_ADDTL_REPLIES_ON_LOAD_MORE=999999 ONBUILD ARG TALK_THREADING_LEVEL=3 ONBUILD ARG TALK_DEFAULT_STREAM_TAB=all ONBUILD ARG TALK_DISABLE_EMBED_POLYFILL=FALSE diff --git a/README.md b/README.md index dd04b096f..e7776865f 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ Online comments are broken. Our open-source commenting platform, Talk, rethinks how moderation, comment display, and conversation function, creating the opportunity for safer, smarter discussions around your work. [Read more about Talk here](https://coralproject.net/talk). -Built with <3 by The Coral Project. +Built with <3 by The Coral Project, a part of [Vox Media](https://www.voxmedia.com/a/go-deeper/about). ## Getting Started @@ -22,13 +22,11 @@ For advanced configuration and usage of Talk, check out our [Configuration](http ## Versions & Upgrading -The current recommended release version is v4.5.0. ^4.5 (and all future even-numbered versions) are considered stable LTS versions. We recommend ^4.5 for use in production environments. +Check our Releases page for the latest recommended release version. [Releases](https://github.com/coralproject/talk/releases) All future even-numbered versions are considered stable LTS versions. We recommend the latest verified release for use in production environments. ## More Resources -- [Talk Product Roadmap](https://www.pivotaltracker.com/n/projects/1863625) -- [Our Blog](https://blog.coralproject.net/) -- [Community Forums](https://community.coralproject.net/) +- [Our Blog](https://coralproject.net/blog) - [Community Guides for Journalism](https://guides.coralproject.net/) - [More About Us](https://coralproject.net/) diff --git a/bin/cli-users b/bin/cli-users index 743119a0a..3e8bc7741 100755 --- a/bin/cli-users +++ b/bin/cli-users @@ -105,7 +105,8 @@ function printUserAsTable(user) { Suspension: user.suspended ? `Until ${user.status.suspension.until}` : false, - } + }, + { 'Always premod comments': user.alwaysPremod } ); console.log(table.toString()); diff --git a/client/coral-admin/src/actions/alwaysPremodUserDialog.js b/client/coral-admin/src/actions/alwaysPremodUserDialog.js new file mode 100644 index 000000000..5c5cfe7b8 --- /dev/null +++ b/client/coral-admin/src/actions/alwaysPremodUserDialog.js @@ -0,0 +1,14 @@ +import { + SHOW_ALWAYS_PREMOD_USER_DIALOG, + HIDE_ALWAYS_PREMOD_USER_DIALOG, +} from '../constants/alwaysPremodUserDialog.js'; + +export const showAlwaysPremodUserDialog = ({ userId, username }) => ({ + type: SHOW_ALWAYS_PREMOD_USER_DIALOG, + userId, + username, +}); + +export const hideAlwaysPremodUserDialog = () => ({ + type: HIDE_ALWAYS_PREMOD_USER_DIALOG, +}); diff --git a/client/coral-admin/src/actions/community.js b/client/coral-admin/src/actions/community.js index c7c7cf2a3..f3bd6301e 100644 --- a/client/coral-admin/src/actions/community.js +++ b/client/coral-admin/src/actions/community.js @@ -9,6 +9,8 @@ import { SET_SEARCH_VALUE, SHOW_BANUSER_DIALOG, HIDE_BANUSER_DIALOG, + SHOW_ALWAYS_PREMOD_USER_DIALOG, + HIDE_ALWAYS_PREMOD_USER_DIALOG, SHOW_REJECT_USERNAME_DIALOG, HIDE_REJECT_USERNAME_DIALOG, SET_INDICATOR_TRACK, @@ -61,6 +63,15 @@ export const setSearchValue = value => ({ export const showBanUserDialog = user => ({ type: SHOW_BANUSER_DIALOG, user }); export const hideBanUserDialog = () => ({ type: HIDE_BANUSER_DIALOG }); +// Always premod User Dialog +export const showAlwaysPremodUserDialog = user => ({ + type: SHOW_ALWAYS_PREMOD_USER_DIALOG, + user, +}); +export const hideAlwaysPremodUserDialog = () => ({ + type: HIDE_ALWAYS_PREMOD_USER_DIALOG, +}); + // Reject Username Dialog export const showRejectUsernameDialog = user => ({ type: SHOW_REJECT_USERNAME_DIALOG, diff --git a/client/coral-admin/src/components/AlwaysPremodUserDialog.css b/client/coral-admin/src/components/AlwaysPremodUserDialog.css new file mode 100644 index 000000000..5bf1beeb7 --- /dev/null +++ b/client/coral-admin/src/components/AlwaysPremodUserDialog.css @@ -0,0 +1,29 @@ +.dialog { + border: none; + box-shadow: 0 9px 46px 8px rgba(0, 0, 0, 0.14), 0 11px 15px -7px rgba(0, 0, 0, 0.12), 0 24px 38px 3px rgba(0, 0, 0, 0.2); + width: 400px; + top: 50%; + transform: translateY(-50%); + padding: 20px; + border-radius: 4px; +} + +.header { + color: black; + font-size: 1.5em; + font-weight: 500; + margin: 0 0 8px 0; +} + +.subheader { + color: black; + font-size: 1.3em; + font-weight: 500; + margin: 0 0 8px 0; +} + +.buttons { + margin-top: 8px; + margin-bottom: 6px; + text-align: right; +} diff --git a/client/coral-admin/src/components/AlwaysPremodUserDialog.js b/client/coral-admin/src/components/AlwaysPremodUserDialog.js new file mode 100644 index 000000000..7d01d6259 --- /dev/null +++ b/client/coral-admin/src/components/AlwaysPremodUserDialog.js @@ -0,0 +1,68 @@ +import React from 'react'; +import cn from 'classnames'; +import PropTypes from 'prop-types'; +import { Dialog } from 'coral-ui'; +import styles from './AlwaysPremodUserDialog.css'; + +import Button from 'coral-ui/components/Button'; +import t from 'coral-framework/services/i18n'; + +class AlwaysPremodUserDialog extends React.Component { + handlePerform = () => { + this.props.onPerform(); + }; + + render() { + const { open, onCancel, username, info } = this.props; + return ( + + + × + +
+

+ {t('alwayspremoddialog.always_premod_user')} +

+

+ {t('alwayspremoddialog.are_you_sure', username)} +

+

{info}

+
+ + +
+
+
+ ); + } +} + +AlwaysPremodUserDialog.propTypes = { + open: PropTypes.bool, + onPerform: PropTypes.func.isRequired, + onCancel: PropTypes.func.isRequired, + username: PropTypes.string, + info: PropTypes.string, +}; + +export default AlwaysPremodUserDialog; diff --git a/client/coral-admin/src/components/UserDetail.css b/client/coral-admin/src/components/UserDetail.css index c35e4776f..eb379c00f 100644 --- a/client/coral-admin/src/components/UserDetail.css +++ b/client/coral-admin/src/components/UserDetail.css @@ -137,6 +137,12 @@ display: inline-block; } +.actionsMenuAlwaysPremod { + background-color: #F0B50B; + border-color: #F0B50B; + color: white; +} + .actionsMenuSuspended { background-color: #F29336; border-color: #F29336; diff --git a/client/coral-admin/src/components/UserDetail.js b/client/coral-admin/src/components/UserDetail.js index 9bde04f96..f6806915d 100644 --- a/client/coral-admin/src/components/UserDetail.js +++ b/client/coral-admin/src/components/UserDetail.js @@ -12,6 +12,7 @@ import { isUsernameRejected, isUsernameChanged, isBanned, + isAlwaysPremod, getKarma, } from 'coral-framework/utils/user'; @@ -45,6 +46,7 @@ function getUserStatusArray(user) { const statusMap = { suspended: isSuspended, banned: isBanned, + alwaysPremod: isAlwaysPremod, usernameRejected: isUsernameRejected, usernameChanged: isUsernameChanged, }; @@ -68,6 +70,12 @@ class UserDetail extends React.Component { username: this.props.root.user.username, }); + showAlwaysPremodUserDialog = () => + this.props.showAlwaysPremodUserDialog({ + userId: this.props.root.user.id, + username: this.props.root.user.username, + }); + showRejectUsernameDialog = () => this.props.showRejectUsernameDialog({ userId: this.props.root.user.id, @@ -107,6 +115,8 @@ class UserDetail extends React.Component { return t('user_detail.suspended'); case 'banned': return t('user_detail.banned'); + case 'alwaysPremod': + return t('user_detail.always_premoded'); case 'usernameRejected': return ( @@ -153,6 +163,7 @@ class UserDetail extends React.Component { toggleSelectAll, unbanUser, unsuspendUser, + removeAlwaysPremodUser, modal, acceptComment, rejectComment, @@ -169,6 +180,7 @@ class UserDetail extends React.Component { const banned = isBanned(user); const suspended = isSuspended(user); + const alwaysPremod = isAlwaysPremod(user); const usernameRejected = isUsernameRejected(user); const usernameChanged = isUsernameChanged(user); @@ -200,6 +212,7 @@ class UserDetail extends React.Component { { [styles.actionsMenuSuspended]: suspended, [styles.actionsMenuBanned]: banned, + [styles.actionsMenuAlwaysPremod]: alwaysPremod, }, 'talk-admin-user-detail-actions-button' )} @@ -231,6 +244,21 @@ class UserDetail extends React.Component { )} + {alwaysPremod ? ( + removeAlwaysPremodUser({ id: user.id })} + > + {t('user_detail.remove_always_premod')} + + ) : ( + + {t('user_detail.always_premod')} + + )} + {usernameChanged && ( {t('user_detail.username_needs_approval')} @@ -476,8 +504,10 @@ UserDetail.propTypes = { showRejectUsernameDialog: PropTypes.func, showSuspendUserDialog: PropTypes.func, showBanUserDialog: PropTypes.func, + showAlwaysPremodUserDialog: PropTypes.func, unbanUser: PropTypes.func.isRequired, unsuspendUser: PropTypes.func.isRequired, + removeAlwaysPremodUser: PropTypes.func.isRequired, modal: PropTypes.bool, rejectUsername: PropTypes.func.isRequired, }; diff --git a/client/coral-admin/src/components/UserHistory.js b/client/coral-admin/src/components/UserHistory.js index 3a3472c86..fb797c1cd 100644 --- a/client/coral-admin/src/components/UserHistory.js +++ b/client/coral-admin/src/components/UserHistory.js @@ -48,6 +48,10 @@ const buildActionResponse = (typename, created_at, until, status) => { return status ? t('user_history.user_banned') : t('user_history.ban_removed'); + case 'AlwaysPremodStatusHistory': + return status + ? t('user_history.user_always_premoded') + : t('user_history.always_premod_removed'); case 'SuspensionStatusHistory': return until ? t('user_history.suspended', readableDuration(created_at, until)) diff --git a/client/coral-admin/src/constants/alwaysPremodUserDialog.js b/client/coral-admin/src/constants/alwaysPremodUserDialog.js new file mode 100644 index 000000000..0728bbc43 --- /dev/null +++ b/client/coral-admin/src/constants/alwaysPremodUserDialog.js @@ -0,0 +1,2 @@ +export const SHOW_ALWAYS_PREMOD_USER_DIALOG = 'SHOW_ALWAYS_PREMOD_USER_DIALOG'; +export const HIDE_ALWAYS_PREMOD_USER_DIALOG = 'HIDE_ALWAYS_PREMOD_USER_DIALOG'; diff --git a/client/coral-admin/src/constants/community.js b/client/coral-admin/src/constants/community.js index 1ffe3f8d6..b47ec0455 100644 --- a/client/coral-admin/src/constants/community.js +++ b/client/coral-admin/src/constants/community.js @@ -14,6 +14,9 @@ export const FETCH_FLAGGED_COMMENTERS_FAILURE = `${prefix}_FETCH_FLAGGED_COMMENT export const SHOW_BANUSER_DIALOG = `${prefix}_SHOW_BANUSER_DIALOG`; export const HIDE_BANUSER_DIALOG = `${prefix}_HIDE_BANUSER_DIALOG`; +export const SHOW_ALWAYS_PREMOD_USER_DIALOG = `${prefix}_SHOW_ALWAYS_PREMOD_USER_DIALOG`; +export const HIDE_ALWAYS_PREMOD_USER_DIALOG = `${prefix}_HIDE_ALWAYS_PREMOD_USER_DIALOG`; + export const SHOW_REJECT_USERNAME_DIALOG = `${prefix}_SHOW_REJECT_USERNAME_DIALOG`; export const HIDE_REJECT_USERNAME_DIALOG = `${prefix}_HIDE_REJECT_USERNAME_DIALOG`; diff --git a/client/coral-admin/src/containers/AlwaysPremodUserDialog.js b/client/coral-admin/src/containers/AlwaysPremodUserDialog.js new file mode 100644 index 000000000..e6e195810 --- /dev/null +++ b/client/coral-admin/src/containers/AlwaysPremodUserDialog.js @@ -0,0 +1,66 @@ +import React, { Component } from 'react'; +import PropTypes from 'prop-types'; +import { connect } from 'react-redux'; +import { bindActionCreators } from 'redux'; +import AlwaysPremodUserDialog from '../components/AlwaysPremodUserDialog'; +import { hideAlwaysPremodUserDialog } from '../actions/alwaysPremodUserDialog'; +import { withAlwaysPremodUser } from 'coral-framework/graphql/mutations'; +import { compose } from 'react-apollo'; +import t from 'coral-framework/services/i18n'; + +class AlwaysPremodUserDialogContainer extends Component { + alwaysPremodUser = async () => { + const { userId, alwaysPremodUser, hideAlwaysPremodUserDialog } = this.props; + await alwaysPremodUser({ id: userId }); + hideAlwaysPremodUserDialog(); + }; + + getInfo() { + let note = t('alwayspremoddialog.note_always_premod_user'); + return t('alwayspremoddialog.note', note); + } + + render() { + return ( + + ); + } +} + +AlwaysPremodUserDialogContainer.propTypes = { + alwaysPremodUser: PropTypes.func.isRequired, + hideAlwaysPremodUserDialog: PropTypes.func, + open: PropTypes.bool, + username: PropTypes.string, +}; + +const mapStateToProps = ({ + alwaysPremodUserDialog: { open, userId, username }, +}) => ({ + open, + userId, + username, +}); + +const mapDispatchToProps = dispatch => ({ + ...bindActionCreators( + { + hideAlwaysPremodUserDialog, + }, + dispatch + ), +}); + +export default compose( + connect( + mapStateToProps, + mapDispatchToProps + ), + withAlwaysPremodUser +)(AlwaysPremodUserDialogContainer); diff --git a/client/coral-admin/src/containers/Layout.js b/client/coral-admin/src/containers/Layout.js index 22c624b7d..10e4666f8 100644 --- a/client/coral-admin/src/containers/Layout.js +++ b/client/coral-admin/src/containers/Layout.js @@ -5,6 +5,7 @@ import Layout from '../components/Layout'; import Login from '../containers/Login'; import { FullLoading } from '../components/FullLoading'; import BanUserDialog from './BanUserDialog'; +import AlwaysPremodUserDialog from './AlwaysPremodUserDialog'; import SuspendUserDialog from './SuspendUserDialog'; import RejectUsernameDialog from './RejectUsernameDialog'; import { toggleModal as toggleShortcutModal } from '../actions/moderation'; @@ -40,6 +41,7 @@ class LayoutContainer extends React.Component { toggleShortcutModal={toggleShortcutModal} currentUser={this.props.currentUser} > + diff --git a/client/coral-admin/src/containers/UserDetail.js b/client/coral-admin/src/containers/UserDetail.js index e9ffea4d1..7053f6c0b 100644 --- a/client/coral-admin/src/containers/UserDetail.js +++ b/client/coral-admin/src/containers/UserDetail.js @@ -22,12 +22,14 @@ import { withSetCommentStatus, withUnbanUser, withUnsuspendUser, + withRemoveAlwaysPremodUser, withRejectUsername, withPostFlag, } from 'coral-framework/graphql/mutations'; import UserDetailComment from './UserDetailComment'; import update from 'immutability-helper'; import { showBanUserDialog } from 'actions/banUserDialog'; +import { showAlwaysPremodUserDialog } from 'actions/alwaysPremodUserDialog'; import { showSuspendUserDialog } from 'actions/suspendUserDialog'; import { showRejectUsernameDialog } from 'actions/rejectUsernameDialog'; @@ -153,6 +155,7 @@ UserDetailContainer.propTypes = { selectedCommentIds: PropTypes.array, unbanUser: PropTypes.func.isRequired, unsuspendUser: PropTypes.func.isRequired, + removeAlwaysPremodUser: PropTypes.func.isRequired, rejectUsername: PropTypes.func.isRequired, userId: PropTypes.string, }; @@ -218,6 +221,17 @@ export const withUserDetailQuery = withQuery( created_at } } + alwaysPremod { + status + history { + status + assigned_by { + id + username + } + created_at + } + }, username { status history { @@ -280,6 +294,7 @@ const mapDispatchToProps = dispatch => ({ ...bindActionCreators( { showBanUserDialog, + showAlwaysPremodUserDialog, showSuspendUserDialog, showRejectUsernameDialog, changeTab, @@ -302,6 +317,7 @@ export default compose( withSetCommentStatus, withUnbanUser, withUnsuspendUser, + withRemoveAlwaysPremodUser, withRejectUsername, withPostFlag, withRouter diff --git a/client/coral-admin/src/graphql/index.js b/client/coral-admin/src/graphql/index.js index 9fca9f497..11eea3d18 100644 --- a/client/coral-admin/src/graphql/index.js +++ b/client/coral-admin/src/graphql/index.js @@ -10,6 +10,9 @@ const userStatusFragment = gql` banned { status } + alwaysPremod { + status + } suspension { until } @@ -125,6 +128,64 @@ export default { }); }, }), + AlwaysPremodUser: ({ + variables: { + input: { id }, + }, + }) => ({ + update: proxy => { + const fragmentId = `User_${id}`; + const data = proxy.readFragment({ + fragment: userStatusFragment, + id: fragmentId, + }); + + const updated = update(data, { + state: { + status: { + alwaysPremod: { + status: { $set: true }, + }, + }, + }, + }); + + proxy.writeFragment({ + fragment: userStatusFragment, + id: fragmentId, + data: updated, + }); + }, + }), + RemoveAlwaysPremodUser: ({ + variables: { + input: { id }, + }, + }) => ({ + update: proxy => { + const fragmentId = `User_${id}`; + const data = proxy.readFragment({ + fragment: userStatusFragment, + id: fragmentId, + }); + + const updated = update(data, { + state: { + status: { + alwaysPremod: { + status: { $set: false }, + }, + }, + }, + }); + + proxy.writeFragment({ + fragment: userStatusFragment, + id: fragmentId, + data: updated, + }); + }, + }), BanUser: ({ variables: { input: { id }, @@ -154,6 +215,23 @@ export default { }); }, }), + SetUserAlwaysPremodStatus: ({ variables: { status, id } }) => ({ + updateQueries: { + TalkAdmin_Community: prev => { + if (!status) { + return prev; + } + const updated = update(prev, { + users: { + nodes: { + $apply: nodes => nodes.filter(node => node.id !== id), + }, + }, + }); + return updated; + }, + }, + }), UnbanUser: ({ variables: { input: { id }, diff --git a/client/coral-admin/src/reducers/alwaysPremodUserDialog.js b/client/coral-admin/src/reducers/alwaysPremodUserDialog.js new file mode 100644 index 000000000..a5da4c54b --- /dev/null +++ b/client/coral-admin/src/reducers/alwaysPremodUserDialog.js @@ -0,0 +1,29 @@ +import { + SHOW_ALWAYS_PREMOD_USER_DIALOG, + HIDE_ALWAYS_PREMOD_USER_DIALOG, +} from '../constants/alwaysPremodUserDialog'; + +const initialState = { + open: false, + userId: null, + username: '', +}; + +export default function alwaysPremodUserDialog(state = initialState, action) { + switch (action.type) { + case SHOW_ALWAYS_PREMOD_USER_DIALOG: + return { + ...state, + open: true, + userId: action.userId, + username: action.username, + }; + case HIDE_ALWAYS_PREMOD_USER_DIALOG: + return { + ...state, + open: false, + }; + default: + return state; + } +} diff --git a/client/coral-admin/src/reducers/index.js b/client/coral-admin/src/reducers/index.js index e806f5d11..cded51943 100644 --- a/client/coral-admin/src/reducers/index.js +++ b/client/coral-admin/src/reducers/index.js @@ -5,6 +5,7 @@ import moderation from './moderation'; import install from './install'; import banUserDialog from './banUserDialog'; import suspendUserDialog from './suspendUserDialog'; +import alwaysPremodUserDialog from './alwaysPremodUserDialog'; import rejectUsernameDialog from './rejectUsernameDialog'; import userDetail from './userDetail'; import ui from './ui'; @@ -14,6 +15,7 @@ export default { banUserDialog, configure, suspendUserDialog, + alwaysPremodUserDialog, userDetail, stories, community, diff --git a/client/coral-admin/src/reducers/stories.js b/client/coral-admin/src/reducers/stories.js index a942dd724..4a069c82e 100644 --- a/client/coral-admin/src/reducers/stories.js +++ b/client/coral-admin/src/reducers/stories.js @@ -11,6 +11,7 @@ const initialState = { criteria: { asc: 'false', filter: 'all', + limit: 20, }, }; diff --git a/client/coral-admin/src/reducers/ui.js b/client/coral-admin/src/reducers/ui.js index dcb9ddbe0..263ae4ddd 100644 --- a/client/coral-admin/src/reducers/ui.js +++ b/client/coral-admin/src/reducers/ui.js @@ -6,6 +6,10 @@ import { SHOW_SUSPEND_USER_DIALOG, HIDE_SUSPEND_USER_DIALOG, } from '../constants/suspendUserDialog'; +import { + SHOW_ALWAYS_PREMOD_USER_DIALOG, + HIDE_ALWAYS_PREMOD_USER_DIALOG, +} from '../constants/alwaysPremodUserDialog'; const initialState = { modal: false, @@ -23,6 +27,11 @@ export default function config(state = initialState, action) { ...state, modal: true, }; + case SHOW_ALWAYS_PREMOD_USER_DIALOG: + return { + ...state, + modal: true, + }; case HIDE_BAN_USER_DIALOG: return { ...state, @@ -33,6 +42,11 @@ export default function config(state = initialState, action) { ...state, modal: false, }; + case HIDE_ALWAYS_PREMOD_USER_DIALOG: + return { + ...state, + modal: false, + }; default: return state; } diff --git a/client/coral-admin/src/routes/Community/components/FlaggedUser.css b/client/coral-admin/src/routes/Community/components/FlaggedUser.css index 436f7307d..251a2df55 100644 --- a/client/coral-admin/src/routes/Community/components/FlaggedUser.css +++ b/client/coral-admin/src/routes/Community/components/FlaggedUser.css @@ -78,9 +78,10 @@ } .flaggedByReason { - font-size: 1tpx; + font-size: 14px; margin: 0px; line-height: 1.4; + word-break: break-word; } diff --git a/client/coral-admin/src/routes/Community/components/People.css b/client/coral-admin/src/routes/Community/components/People.css index aea321cd7..4b7cfd4a8 100644 --- a/client/coral-admin/src/routes/Community/components/People.css +++ b/client/coral-admin/src/routes/Community/components/People.css @@ -130,6 +130,12 @@ th.header:nth-child(2), th.header:nth-child(3) { font-size: 0.98em; } +.actionsMenuAlwaysPremod { + background-color: #F0B50B; + border-color: #F0B50B; + color: white; +} + .actionsMenuSuspended { background-color: #F29336; border-color: #F29336; diff --git a/client/coral-admin/src/routes/Community/components/People.js b/client/coral-admin/src/routes/Community/components/People.js index e40b6cca4..cbcb74bc9 100644 --- a/client/coral-admin/src/routes/Community/components/People.js +++ b/client/coral-admin/src/routes/Community/components/People.js @@ -8,7 +8,11 @@ import LoadMore from '../../../components/LoadMore'; import PropTypes from 'prop-types'; import ActionsMenu from 'coral-admin/src/components/ActionsMenu'; import ActionsMenuItem from 'coral-admin/src/components/ActionsMenuItem'; -import { isSuspended, isBanned } from 'coral-framework/utils/user'; +import { + isSuspended, + isBanned, + isAlwaysPremod, +} from 'coral-framework/utils/user'; import { RadioGroup, Radio } from 'react-mdl'; import moment from 'moment'; import { @@ -43,6 +47,8 @@ class People extends React.Component { return 'Banned'; } else if (isSuspended(user)) { return 'Suspended'; + } else if (isAlwaysPremod(user)) { + return 'Always premoderated'; } return ''; }; @@ -55,6 +61,10 @@ class People extends React.Component { this.props.unbanUser(input); }; + removeAlwaysPremodUser = input => { + this.props.removeAlwaysPremodUser(input); + }; + showBanUserDialog = input => { this.props.showBanUserDialog(input); }; @@ -63,6 +73,10 @@ class People extends React.Component { this.props.showSuspendUserDialog(input); }; + showAlwaysPremodUserDialog = input => { + this.props.showAlwaysPremodUserDialog(input); + }; + render() { const { onFilterChange, @@ -109,6 +123,7 @@ class People extends React.Component { {t('community.active')} {t('community.suspended')} {t('community.banned')} + {t('community.always_premod')}
{t('community.filter_role')} @@ -188,6 +203,9 @@ class People extends React.Component { user ), [styles.actionsMenuBanned]: isBanned(user), + [styles.actionsMenuAlwaysPremod]: isAlwaysPremod( + user + ), }, 'talk-admin-user-detail-actions-button' )} @@ -232,6 +250,27 @@ class People extends React.Component { {t('modqueue.ban_user_actions')} )} + + {isAlwaysPremod(user) ? ( + + this.removeAlwaysPremodUser({ id: user.id }) + } + > + Remove Always Premoderate + + ) : ( + + this.showAlwaysPremodUserDialog({ + userId: user.id, + username: user.username, + }) + } + > + {t('modqueue.always_premod_user_actions')} + + )} @@ -294,8 +333,10 @@ People.propTypes = { viewUserDetail: PropTypes.func.isRequired, unbanUser: PropTypes.func.isRequired, unsuspendUser: PropTypes.func.isRequired, + removeAlwaysPremodUser: PropTypes.func.isRequired, showSuspendUserDialog: PropTypes.func, showBanUserDialog: PropTypes.func, + showAlwaysPremodUserDialog: PropTypes.func, loadMore: PropTypes.func.isRequired, }; diff --git a/client/coral-admin/src/routes/Community/containers/People.js b/client/coral-admin/src/routes/Community/containers/People.js index 7f47795b4..57fb7fa8e 100644 --- a/client/coral-admin/src/routes/Community/containers/People.js +++ b/client/coral-admin/src/routes/Community/containers/People.js @@ -8,9 +8,11 @@ import { withUnbanUser, withUnsuspendUser, withSetUserRole, + withRemoveAlwaysPremodUser, } from 'coral-framework/graphql/mutations'; import { showBanUserDialog } from 'actions/banUserDialog'; import { showSuspendUserDialog } from 'actions/suspendUserDialog'; +import { showAlwaysPremodUserDialog } from 'actions/alwaysPremodUserDialog'; import { viewUserDetail } from '../../../actions/userDetail'; import { appendNewNodes } from 'plugin-api/beta/client/utils'; import update from 'immutability-helper'; @@ -30,6 +32,7 @@ class PeopleContainer extends React.PureComponent { active: { suspended: false, banned: false }, suspended: { suspended: true }, banned: { banned: true }, + alwaysPremod: { alwaysPremod: true }, }; onFilterChange = filter => e => @@ -40,7 +43,6 @@ class PeopleContainer extends React.PureComponent { getFilterState = () => { const { role, status } = this.state; - return { status: this.statusToQuery[status] || null, role: role || null, @@ -106,8 +108,10 @@ class PeopleContainer extends React.PureComponent { setUserRole={this.props.setUserRole} showSuspendUserDialog={this.props.showSuspendUserDialog} showBanUserDialog={this.props.showBanUserDialog} + showAlwaysPremodUserDialog={this.props.showAlwaysPremodUserDialog} unbanUser={this.props.unbanUser} unsuspendUser={this.props.unsuspendUser} + removeAlwaysPremodUser={this.props.removeAlwaysPremodUser} data={this.props.data} root={this.props.root} users={this.props.root.users} @@ -122,9 +126,11 @@ PeopleContainer.propTypes = { setUserRole: PropTypes.func.isRequired, unbanUser: PropTypes.func.isRequired, unsuspendUser: PropTypes.func.isRequired, + removeAlwaysPremodUser: PropTypes.func.isRequired, viewUserDetail: PropTypes.func.isRequired, showSuspendUserDialog: PropTypes.func, showBanUserDialog: PropTypes.func, + showAlwaysPremodUserDialog: PropTypes.func, data: PropTypes.object, root: PropTypes.object, }; @@ -153,6 +159,9 @@ const LOAD_MORE_QUERY = gql` banned { status } + alwaysPremod { + status + } suspension { until } @@ -187,6 +196,9 @@ const FILTER_QUERY = gql` banned { status } + alwaysPremod { + status + } suspension { until } @@ -203,6 +215,7 @@ const mapDispatchToProps = dispatch => viewUserDetail, showSuspendUserDialog, showBanUserDialog, + showAlwaysPremodUserDialog, }, dispatch ); @@ -215,6 +228,7 @@ export default compose( withSetUserRole, withUnsuspendUser, withUnbanUser, + withRemoveAlwaysPremodUser, withQuery( gql` query TalkAdmin_Community_People { @@ -236,6 +250,9 @@ export default compose( banned { status } + alwaysPremod { + status + } suspension { until } diff --git a/client/coral-embed-stream/src/constants/stream.js b/client/coral-embed-stream/src/constants/stream.js index 64d4e64d3..3de1453cb 100644 --- a/client/coral-embed-stream/src/constants/stream.js +++ b/client/coral-embed-stream/src/constants/stream.js @@ -5,6 +5,9 @@ const prefix = 'TALK_EMBED_STREAM'; export const ADDTL_COMMENTS_ON_LOAD_MORE = parseInt( defaultTo(process.env.TALK_ADDTL_COMMENTS_ON_LOAD_MORE, '10') ); +export const ADDTL_REPLIES_ON_LOAD_MORE = parseInt( + defaultTo(process.env.TALK_ADDTL_REPLIES_ON_LOAD_MORE, '999999') +); export const ASSET_COMMENTS_LOAD_DEPTH = parseInt( defaultTo(process.env.TALK_ASSET_COMMENTS_LOAD_DEPTH, '10') ); diff --git a/client/coral-embed-stream/src/containers/Embed.js b/client/coral-embed-stream/src/containers/Embed.js index 836def259..ef7c361a5 100644 --- a/client/coral-embed-stream/src/containers/Embed.js +++ b/client/coral-embed-stream/src/containers/Embed.js @@ -175,6 +175,9 @@ const USER_BANNED_SUBSCRIPTION = gql` banned { status } + alwaysPremod { + status + } suspension { until } @@ -196,6 +199,9 @@ const USER_SUSPENDED_SUBSCRIPTION = gql` banned { status } + alwaysPremod { + status + } suspension { until } @@ -217,6 +223,9 @@ const USERNAME_REJECTED_SUBSCRIPTION = gql` banned { status } + alwaysPremod { + status + } suspension { until } @@ -253,6 +262,9 @@ const EMBED_QUERY = gql` banned { status } + alwaysPremod { + status + } suspension { until } diff --git a/client/coral-embed-stream/src/tabs/stream/containers/Stream.js b/client/coral-embed-stream/src/tabs/stream/containers/Stream.js index 1f32fcedf..4390797f9 100644 --- a/client/coral-embed-stream/src/tabs/stream/containers/Stream.js +++ b/client/coral-embed-stream/src/tabs/stream/containers/Stream.js @@ -6,6 +6,7 @@ import { bindActionCreators } from 'redux'; import { ADDTL_COMMENTS_ON_LOAD_MORE, ASSET_COMMENTS_LOAD_DEPTH, + ADDTL_REPLIES_ON_LOAD_MORE, THREADING_LEVEL, } from '../../../constants/stream'; import { @@ -152,7 +153,9 @@ class StreamContainer extends React.Component { return this.props.data.fetchMore({ query: LOAD_MORE_QUERY, variables: { - limit: parent_id ? 999999 : ADDTL_COMMENTS_ON_LOAD_MORE, + limit: parent_id + ? ADDTL_REPLIES_ON_LOAD_MORE + : ADDTL_COMMENTS_ON_LOAD_MORE, cursor: comment.replies.endCursor, parent_id, asset_id: this.props.asset.id, @@ -398,6 +401,9 @@ const fragments = { banned { status } + alwaysPremod { + status + } suspension { until } diff --git a/client/coral-embed/src/SnackBar.js b/client/coral-embed/src/SnackBar.js index 67851ce4e..ca9c813d4 100644 --- a/client/coral-embed/src/SnackBar.js +++ b/client/coral-embed/src/SnackBar.js @@ -7,17 +7,25 @@ const DEFAULT_STYLE = { willChange: 'transform, opacity', transition: 'transform .35s cubic-bezier(.55,0,.1,1), opacity .35s', pointerEvents: 'none', - padding: '12px 18px', + padding: '12px 18px 0', color: '#fff', - borderRadius: '3px 3px 0 0', - textAlign: 'center', + borderRadius: '4px', maxWidth: '400px', left: '50%', opacity: 0, transform: 'translate(-50%, 20px)', - bottom: 0, + bottom: '20px', boxSizing: 'border-box', fontFamily: 'Helvetica, "Helvetica Neue", Verdana, sans-serif', + display: 'flex', +}; + +const CLOSE_STYLE = { + fontSize: '20px', + cursor: 'pointer', + marginLeft: '10px', + position: 'relative', + top: '-4px', }; export default class Snackbar { @@ -26,6 +34,20 @@ export default class Snackbar { this.el = document.createElement('div'); this.el.id = 'coral-notif'; + const closeButton = document.createElement('div'); + closeButton.className = 'coral-notif-close'; + for (let key in CLOSE_STYLE) { + closeButton.style[key] = CLOSE_STYLE[key]; + } + closeButton.textContent = '×'; + closeButton.onclick = () => this.clear(); + + this.snackbarText = document.createElement('div'); + this.snackbarText.className = 'coral-notif-text'; + this.snackbarText.style.paddingBottom = '12px'; + this.el.appendChild(this.snackbarText); + this.el.appendChild(closeButton); + // Apply custom styles to the snackbar. const style = Object.assign({}, DEFAULT_STYLE, customStyle); for (let key in style) { @@ -35,6 +57,7 @@ export default class Snackbar { clear() { this.el.style.opacity = 0; + this.el.style.pointerEvents = 'none'; } alert(message) { @@ -42,7 +65,7 @@ export default class Snackbar { this.el.style.transform = 'translate(-50%, 20px)'; this.el.style.opacity = 0; this.el.className = `coral-notif-${type}`; - this.el.textContent = text; + this.snackbarText.textContent = text; if (this.timeout) { clearTimeout(this.timeout); @@ -51,10 +74,12 @@ export default class Snackbar { this.timeout = setTimeout(() => { this.el.style.transform = 'translate(-50%, 0)'; this.el.style.opacity = 1; + this.el.style.pointerEvents = 'auto'; this.timeout = setTimeout(() => { this.el.style.opacity = 0; - }, 7000); + this.el.style.pointerEvents = 'none'; + }, 15000); }, 0); } diff --git a/client/coral-framework/graphql/fragments.js b/client/coral-framework/graphql/fragments.js index 778214b8b..976776933 100644 --- a/client/coral-framework/graphql/fragments.js +++ b/client/coral-framework/graphql/fragments.js @@ -3,6 +3,7 @@ import { createDefaultResponseFragments } from '../utils'; // fragments defined here are automatically registered. export default { ...createDefaultResponseFragments( + 'AlwaysPremodUserResponse', 'BanUsersResponse', 'ChangeUsernameResponse', 'CloseAssetResponse', @@ -14,6 +15,7 @@ export default { 'IgnoreUserResponse', 'ModifyTagResponse', 'PostFlagResponse', + 'RemoveAlwaysPremodUserResponse', 'SetCommentStatusResponse', 'SetUsernameResponse', 'SetUsernameStatusResponse', diff --git a/client/coral-framework/graphql/mutations.js b/client/coral-framework/graphql/mutations.js index 8191c2cd0..78c044270 100644 --- a/client/coral-framework/graphql/mutations.js +++ b/client/coral-framework/graphql/mutations.js @@ -412,6 +412,48 @@ export const withSetUsername = withMutation( } ); +export const withAlwaysPremodUser = withMutation( + gql` + mutation AlwaysPremodUser($input: AlwaysPremodUserInput!) { + alwaysPremodUser(input: $input) { + ...AlwaysPremodUserResponse + } + } + `, + { + props: ({ mutate }) => ({ + alwaysPremodUser: input => { + return mutate({ + variables: { + input, + }, + }); + }, + }), + } +); + +export const withRemoveAlwaysPremodUser = withMutation( + gql` + mutation RemoveAlwaysPremodUser($input: RemoveAlwaysPremodUserInput!) { + removeAlwaysPremodUser(input: $input) { + ...RemoveAlwaysPremodUserResponse + } + } + `, + { + props: ({ mutate }) => ({ + removeAlwaysPremodUser: input => { + return mutate({ + variables: { + input, + }, + }); + }, + }), + } +); + export const withBanUser = withMutation( gql` mutation BanUser($input: BanUserInput!) { diff --git a/client/coral-framework/services/i18n.js b/client/coral-framework/services/i18n.js index 87d74b3f2..e8c58bd3a 100644 --- a/client/coral-framework/services/i18n.js +++ b/client/coral-framework/services/i18n.js @@ -12,6 +12,7 @@ import 'moment/locale/da'; import 'moment/locale/de'; import 'moment/locale/es'; import 'moment/locale/fr'; +import 'moment/locale/he'; import 'moment/locale/it'; import 'moment/locale/nl'; import 'moment/locale/pt-br'; @@ -23,6 +24,7 @@ import daTA from 'timeago.js/locales/da'; import deTA from 'timeago.js/locales/de'; import esTA from 'timeago.js/locales/es'; import frTA from 'timeago.js/locales/fr'; +import heTA from 'timeago.js/locales/he'; import itTA from 'timeago.js/locales/it'; import nlTA from 'timeago.js/locales/nl'; import pt_BRTA from 'timeago.js/locales/pt_BR'; @@ -36,6 +38,7 @@ import da from '../../../locales/da.yml'; import de from '../../../locales/de.yml'; import es from '../../../locales/es.yml'; import fr from '../../../locales/fr.yml'; +import he from '../../../locales/he.yml'; import it from '../../../locales/it.yml'; import nl_NL from '../../../locales/nl_NL.yml'; import pt_BR from '../../../locales/pt_BR.yml'; @@ -64,6 +67,7 @@ export const translations = { ...de, ...es, ...fr, + ...he, ...it, ...nl_NL, ...pt_BR, @@ -110,6 +114,7 @@ export function setupTranslations() { ta.register('da', daTA); ta.register('de', deTA); ta.register('fr', frTA); + ta.register('he', heTA); ta.register('it', itTA); ta.register('nl_NL', nlTA); ta.register('pt_BR', pt_BRTA); diff --git a/client/coral-framework/services/store.js b/client/coral-framework/services/store.js index b3a470170..ea23eb1f4 100644 --- a/client/coral-framework/services/store.js +++ b/client/coral-framework/services/store.js @@ -14,9 +14,9 @@ import { export function createStore(reducers, middlewares = []) { const enhancers = [applyMiddleware(...middlewares)]; - if (window.devToolsExtension) { + if (window.__REDUX_DEVTOOLS_EXTENSION__) { // we can't have the last argument of compose() be undefined - enhancers.push(window.devToolsExtension()); + enhancers.push(window.__REDUX_DEVTOOLS_EXTENSION__()); } return reduxCreateStore(combineReducers(reducers), {}, compose(...enhancers)); diff --git a/client/coral-framework/utils/user.js b/client/coral-framework/utils/user.js index cdc0884d5..174815bfb 100644 --- a/client/coral-framework/utils/user.js +++ b/client/coral-framework/utils/user.js @@ -35,6 +35,15 @@ export const isBanned = user => { return get(user, 'state.status.banned.status'); }; +/** + * isAlwaysPremod + * retrieves boolean based on the user premod status + */ + +export const isAlwaysPremod = user => { + return get(user, 'state.status.alwaysPremod.status'); +}; + /** * isUsernameRejected * retrieves boolean based on the username status diff --git a/client/coral-ui/components/Paginate.css b/client/coral-ui/components/Paginate.css index 813786d66..c188ccf57 100644 --- a/client/coral-ui/components/Paginate.css +++ b/client/coral-ui/components/Paginate.css @@ -16,7 +16,8 @@ text-align: center; vertical-align: middle; line-height: 30px; - width: 30px; + min-width: 30px; + padding: 5px; user-select: none; } @@ -24,7 +25,14 @@ font-size: 1.8em; } -.active { +.page:hover { + background-color: #ababab; + box-shadow: 0 2px 2px 0 rgba(0,0,0,.14), 0 3px 1px -2px rgba(0,0,0,.2), 0 1px 5px 0 rgba(0,0,0,.12); + a { + color: white; + } +} +.active, .active:hover { background-color: #696969; box-shadow: 0 2px 2px 0 rgba(0,0,0,.14), 0 3px 1px -2px rgba(0,0,0,.2), 0 1px 5px 0 rgba(0,0,0,.12); a { diff --git a/docs/_config.yml b/docs/_config.yml index d9de09850..f63c59213 100644 --- a/docs/_config.yml +++ b/docs/_config.yml @@ -166,6 +166,8 @@ sidebar: url: /customizing-plugins-coral-ui/ - title: When You've Installed Talk url: /when-youve-installed-talk/ + - title: Embedding Talk in Native Mobile Apps + url: /embedding-in-native-mobile-apps/ - title: Migrating children: - title: Migrating from v3.x.x diff --git a/docs/source/01-04-planning-architecture.md b/docs/source/01-04-planning-architecture.md index d11ca4ac2..602222b8f 100644 --- a/docs/source/01-04-planning-architecture.md +++ b/docs/source/01-04-planning-architecture.md @@ -19,3 +19,40 @@ Application servers: c4.xlarge (16 VM nginx + Talk VM machine pairs) Mongo nodes: 3x c3.medium (large db cluster, 1 master, 2 read replicas) If you need help with Talk performance or want custom scaling help or recommendations, let us know by logging a ticket and one of our engineers will get in touch with you: https://support.coralproject.net + +## How to Scale Talk Components +### Scaling the Talk Application + +In addition to scaling by adding additional app servers, Talk as a server component can flex to various roles. Depending on the desired configuration the roles can be split up to ensure high availability, and best matching to underlying host resources. + +There are three components to the Talk application that can be served independently or in combination on a single thread: API Server, WebSockets, and Jobs + +![ServerArchitectureDiagram](/talk/images/ServerArch.png) + +In the diagram we see the difference between a Typical configuration (which just adds additional Talk application instances) vs the Split configuration where different Talk components could be spread across different machine resources to optimize infrastructure utilization. +See [Serving the Application](/talk/configuration-cli-tools/#serving-the-application) + +### Scaling Redis + +Redis serves as a general cache and pub/sub broker. We treat all the data stored in Redis as ephemeral, so using as a cache serves us well for Talk to synchronize expensive query caches. It also serves as our pub/sub broker for use with live updates as propagated through the GraphQL subscription system. For this reason it is not recommended to implement multiple instances of Redis. + +### Scaling MongoDB + +MongoDB is treated as our general store for persisted data. Talk supports the most common strategies for scaling MongoDB instances including replicas and/or sharding. Depending on your specific data use cases, refer to MongoDBs documentation for more information about scaling https://www.mongodb.com/mongodb-scale. + +### Load Balancer +While this subject lives outside the Talk ecosystem, it is critical for application delivery. For websockets to work correctly, a load balancer must be selected that can support long lived connections that are required for websockets to work. + +## Running Talk in Production + +When you are ready to launch your production instance of Talk update your NODE_ENV environment variable from `development` to `production` mode. + +Then launch talk with `yarn start` or with the command `NODE_ENV=production ./bin/cli-serve -j -w` + +By default Talk will run on a single thread, but you can also run multiple Talk threads on a single application instance by setting the environment variable `TALK_CONCURRENCY`. [See Advanced Configuration](/talk/advanced-configuration/#talk-concurrency) + + + + + + diff --git a/docs/source/01-05-pre-launch-checklist.md b/docs/source/01-05-pre-launch-checklist.md index 01e42b9bd..f07953eb6 100644 --- a/docs/source/01-05-pre-launch-checklist.md +++ b/docs/source/01-05-pre-launch-checklist.md @@ -58,9 +58,10 @@ permalink: /pre-launch-checklist/ - Install [talk-plugin-rich-text](/talk/plugin/talk-plugin-rich-text) -- [ ] Do you want to display comment counts? - - Use the GraphQL [CommentCountQuery](https://docs.coralproject.net/talk/api/graphql/#CommentCountQuery) - - Install [talk-plugin-deep-reply-count](/talk/plugin/talk-plugin-deep-reply-count) if necessary. +- [ ] Do you want to display comment counts on your embed stream or on a homepage with dozens of articles? + - Install [talk-plugin-comment-count](https://github.com/coralproject/talk-plugin-comment-count) for summary counts on multiple articles + - Install [talk-plugin-deep-reply-count](/talk/plugin/talk-plugin-deep-reply-count) to add counts to the embed stream + - Or use the GraphQL [CommentCountQuery](https://docs.coralproject.net/talk/api/graphql/#CommentCountQuery) - [ ] Do you want to translate Talk to a different language? diff --git a/docs/source/02-02-advanced-configuration.md b/docs/source/02-02-advanced-configuration.md index 49769be0b..0c70fad91 100644 --- a/docs/source/02-02-advanced-configuration.md +++ b/docs/source/02-02-advanced-configuration.md @@ -23,6 +23,10 @@ otherwise the application will fail to start. Configure the duration for which comment counts are cached for, parsed by [ms](https://www.npmjs.com/package/ms). (Default `1hr`) +## TALK_CONCURRENCY + +This environment variable allows multiple worker processes to be spawned to handle traffic. (Default `1`) + ## TALK_DEFAULT_LANG This is a **Build Variable** and must be consumed during build. If using the @@ -589,3 +593,13 @@ can be used to set an authorization header, or change the user agent. (Default ## TALK_SCRAPER_PROXY_URL Sets a specific HTTP/S proxy to be used by the Asset Scraper using [https-proxy-agent](https://www.npmjs.com/package/https-proxy-agent). (Default `null`) + +## TALK_ADDTL_REPLIES_ON_LOAD_MORE + +This is a **Build Variable** and must be consumed during build. If using the +[Docker-onbuild](/talk/installation-from-docker/#onbuild) +image you can specify it with `--build-arg TALK_ADDTL_REPLIES_ON_LOAD_MORE=3`. + +Specifies the number of replies to load for a comment when clicking "Load More". +It is defaulted quite high to sort of support "loading the rest of the replies". +(Default `999999`) diff --git a/docs/source/05-04-native-mobile-apps.md b/docs/source/05-04-native-mobile-apps.md new file mode 100644 index 000000000..f53a26d57 --- /dev/null +++ b/docs/source/05-04-native-mobile-apps.md @@ -0,0 +1,10 @@ +--- +title: Embedding Talk in Native Mobile Apps +permalink: /embedding-in-native-mobile-apps/ +--- + +When it comes mobile devices, the Talk client embed is already responsive and mobile friendly. We do not currently have plans to provide mobile SDKs, however, you can do this yourself. + +One organization that has taken on the challenge of creating their own iOS native Talk client is [IGN](http://www.ign.com/). They were kind enough to share how they implemented Talk natively on their apps, which can be found [here](http://coralproject.net/wp-content/uploads/2019/01/IGNNativeComments.pdf). + +If you implement Talk on your mobile apps and would like to contribute your process for others, you can add those here in our docs via a PR or post an issue on Github and we can help you. diff --git a/docs/source/07-01-faq.md b/docs/source/07-01-faq.md index 8f34adbf1..f3bf92396 100644 --- a/docs/source/07-01-faq.md +++ b/docs/source/07-01-faq.md @@ -11,8 +11,6 @@ To log a bug or request a feature, submit a Support ticket ([support@coralprojec You can also request help on Github by [submitting an issue](https://github.com/coralproject/talk/issues). This also increases your chances of having someone from the community respond to help. -And you can also search our [Coral Community](https://community.coralproject.net) to see if your issue has been solved, or to get tips from the community. - ## How can our dev team contribute to Talk? We are lucky to work with newsroom dev teams and individual contributors who span the world, and come from newsrooms of all sizes. You can read our [Contribution Guide](https://github.com/coralproject/talk/blob/master/CONTRIBUTING.md) to get started, but feel free to reach out to us via Github too. diff --git a/docs/source/07-02-troubleshooting-tips.md b/docs/source/07-02-troubleshooting-tips.md index 6d7304a83..0b4a7a682 100644 --- a/docs/source/07-02-troubleshooting-tips.md +++ b/docs/source/07-02-troubleshooting-tips.md @@ -38,3 +38,6 @@ If you're using `talk-plugin-auth`: * See if you can isolate if it's a particular group of users that are experiencing this issue, e.g. mods, admins, subscribers? Confirm they have the appropriate permissions to comment. * Note if this is a new issue that happened after an upgrade - did you read the [migration docs](/talk/migration/3/) and the [release notes](https://github.com/coralproject/talk/releases)? This might help you resolve the issue. * If you're still experiencing issues, log a [support ticket](mailto:support@coralproject.net) so we can help diagnose the issue + +If a user has been locked out due to too many failed login attempts: +* How long does the user have to wait before they will be allowed to login? 10 mins \ No newline at end of file diff --git a/docs/source/_data/plugins.yml b/docs/source/_data/plugins.yml index 0e09de210..3c08715fe 100644 --- a/docs/source/_data/plugins.yml +++ b/docs/source/_data/plugins.yml @@ -146,3 +146,8 @@ tags: - default - sorting +- name: talk-plugin-comment-count + description: This plugin enables adding comment counts to Talk’s embed or in other locations on your site. + link: https://github.com/coralproject/talk-plugin-comment-count + tags: + - counts diff --git a/docs/source/images/IGNNativeComments.pdf b/docs/source/images/IGNNativeComments.pdf new file mode 100644 index 000000000..33faf5463 Binary files /dev/null and b/docs/source/images/IGNNativeComments.pdf differ diff --git a/docs/source/images/ServerArch.png b/docs/source/images/ServerArch.png new file mode 100644 index 000000000..aed5ffcca Binary files /dev/null and b/docs/source/images/ServerArch.png differ diff --git a/docs/source/integrating/authentication.md b/docs/source/integrating/authentication.md index f834f3e53..05f88449e 100644 --- a/docs/source/integrating/authentication.md +++ b/docs/source/integrating/authentication.md @@ -26,6 +26,9 @@ Plugins are available for the following 3rd party authentication providers: * [Facebook](/talk/plugin/talk-plugin-facebook-auth/) * [Google](/talk/plugin/talk-plugin-google-auth/) +_FAQ: Can I create a Twitter auth plugin?_ +This is currently not possible because Talk uses passport.js which does not support Twitter's oAuth2 requirements. + ## Custom Token Integration You can integrate Talk with any authentication service to enable single sign-on @@ -61,6 +64,9 @@ The generated JWT must contain the following claims: - [`iss`](https://tools.ietf.org/html/rfc7519#section-4.1.1): the issuer for the token must match the value of `TALK_JWT_ISSUER` - [`aud`](https://tools.ietf.org/html/rfc7519#section-4.1.3): the audience for the token must match the value of `TALK_JWT_AUDIENCE` +### Generate a key to sign the JWT +Optionally you can use https://github.com/coralproject/coralcert to generate a key with which to sign the JWTs and specify the secret as an environment variable. + ### Push token into embed We're assuming that your CMS is capable of authenticating a user account, or diff --git a/docs/source/integrating/notifications.md b/docs/source/integrating/notifications.md index a5f625083..04fe8f59f 100644 --- a/docs/source/integrating/notifications.md +++ b/docs/source/integrating/notifications.md @@ -3,22 +3,13 @@ title: Notifications permalink: /integrating/notifications/ --- -Talk currently supports 3 types of email notifications. +There are several plugins included with Talk that once enabled will control how and when Talk sends emails to users based on user activity. Only basic user profile notifications are enabled by default. - -1. When someone replies to my comment -2. When a staff member replies to my comment -3. When my comment gets featured - -Talk support 3 options for notification frequency: immediately, hourly or daily. Commenters can also opt-out of email notifications. Notifications are set to OFF by default. - -Commenters cannot enable notifications until they have verified their email. - -Note: Notifications are not currently supported for users that sign-up via Facebook or Google auth, or don't have an email attached to their account for any other reason. +_NOTE: Notifications are only supported for users that have an email address in Talk! If you are using authenticate via Facebook, Google, or SSO and you want to persist the user’s email, you will also need to enable `talk-plugin-local-auth` ([See Authentication](/talk/integrating/authentication/))_ ### Configuring SMTP -You must setup SMTP to use notifications. The following ENV variables must be set: +You must setup SMTP to send email notifications. The following ENV variables must be set: ``` TALK_SMTP_FROM_ADDRESS=email@email.com @@ -28,28 +19,74 @@ TALK_SMTP_HOST=smtp.domain.net TALK_SMTP_PORT=2525 ``` -### Enabling Notifications +See our documentation on [setting env variables](/talk/advanced-configuration/#talk-smtp-from-address) for reference. -Enabling the `talk-plugin-notifications` creates a NotificationManager that creates and manages events send from the event emitter that is linked to the Graph API PubSub system. +When running Talk in production we recommend using a 3rd party mail service provider like SendGrid or MailGun. -Adding the `talk-plugin-notifications` plugin will also enable the `notifications` plugin hook. Any plugin that registers before the `talk-plugin-notifications` plugin will get picked up by. +If you are having difficulty with your SMTP settings you can add `DEBUG=talk:jobs:mailer` to your .env to see additional logs from the mailer service. -See https://github.com/coralproject/talk/blob/8b669a31c551a042f0f079d8cfc16825673eb8f0/plugins/talk-plugin-notifications-reply/index.js for an example. +All notifications require SMTP settings to be correctly configured first, then enable the corresponding plugin, and configure any settings in the Talk Admin. If SMTP is not enabled, it will log to the server console that some variables were not provided. -### Notification Categories +### Notification Types & Plugins -Talk currently supports the following Notifications options out of the box: +In addition to the core notifications included with Talk, some plugins add features that are supported by and will add additional types of email notifications. Commenters can opt-out of most email notifications, and comment activity notifications are set to OFF by default. Commenters cannot enable notifications until they have verified their email. -`talk-plugin-notifications-category-reply` -`talk-plugin-notifications-category-staff-reply` -`talk-plugin-notifications-category-featured` +You can see a list of all the plugins related to notifications by visiting: +https://docs.coralproject.net/talk/plugins-directory/?q=notifications + +#### Type: User Profile Notifications (Included by Talk core) + +* When user registers, sends email confirmation and verification +* When user requests password reset +* When user changes username, sends confirmation +* When a user is banned or suspended + +_NOTE: Users can not opt-out of User Profile Notifications_ + +#### Type: GDPR Notifications (optional) +`talk-plugin-profile-data:` +* When my comment history is ready for download (only one link can be generated every 7 days, and the link is valid for 24 hours) + +`talk-plugin-local-auth:` +* When user changes their email address +* When a user adds a new email address + +#### Type: Comment Activity Notifications (optional) +Talk support 3 options for notification frequency for this type of notification: +* immediately +* hourly +* daily + +`talk-plugin-notifications:` +* Enables notifications configuration (required for all comment activity notifications below) + +`talk-plugin-notifications-category-reply:` +* When someone replies to my comment + +`talk-plugin-notifications-category-staff-reply:` +* When a staff member replies to my comment + +`talk-plugin-notifications-category-featured:` +* When my comment gets featured ### Notification Digests +Notification digests are enabled by enabling the corresponding plugin, otherwise comment activity notifications are sent immediately. +* `talk-plugin-notifications-digest-daily` +* `talk-plugin-notifications-digest-hourly` + Talk supports hourly and daily digests out the box, if you would like to create your own, refer to the below: https://github.com/coralproject/talk/blob/9cc9969320dca47bb0f8f81e8d944ae4d19e548b/plugins/talk-plugin-notifications/server/connect.js#L69-L102 + +### Customizing Notifications + +Enabling the `talk-plugin-notifications` creates a NotificationManager that creates and manages events send from the event emitter that is linked to the Graph API PubSub system. This allows the instance that received the mutation to also fire off a notification job that can be handled. It also enabels the `notifications` plugin hook. Any plugin that registers after the `talk-plugin-notifications` can export the notifications plugin to reference it. + +See https://github.com/coralproject/talk/blob/8b669a31c551a042f0f079d8cfc16825673eb8f0/plugins/talk-plugin-notifications-reply/index.js for an example. + + ### Connect API This exposes the `graph/connectors.js` via the `connect` hook. @@ -67,3 +104,12 @@ See https://github.com/coralproject/talk/blob/90290cfa2de88e62f687e1ed0235ba6dfe ### Email Templates Email templates are text based and support translations. If you would like to create a new email template, you can register it via the Connect API, see https://github.com/coralproject/talk/blob/8b669a31c551a042f0f079d8cfc16825673eb8f0/plugins/talk-plugin-notifications/server/connect.js#L12-L28### + +Any email template registered with the same name as another template will replace the existing template for that type of email. _NOTE: that also means that the template data sent to each email template will __not__ change, so bear that in mind when designing templates that you may not have rich access to data._ + +An example of this is with the `talk-plugin-notifications-category-featured` plugin: + +Notification translation is located: https://github.com/coralproject/talk/blob/dd0601a80132d2849c53ef7eaf12ff382f3920b9/plugins/talk-plugin-notifications-category-featured/translations.yml#L13 + +Where the template content is provided: https://github.com/coralproject/talk/blob/dd0601a80132d2849c53ef7eaf12ff382f3920b9/plugins/talk-plugin-notifications-category-featured/index.js#L79 + diff --git a/docs/source/plugins/overview.md b/docs/source/plugins/overview.md index 1f734edeb..b4e1f41d5 100644 --- a/docs/source/plugins/overview.md +++ b/docs/source/plugins/overview.md @@ -67,9 +67,9 @@ External plugins can be resolved by running: This achieves two things: 1. It will traverse into local plugin folders and install their dependencies. - _Note that if the plugin is already installed and available in the + __Note that if the plugin is already installed and available in the node_modules folder, it will not be fetched again unless there is a version - mismatch._ This will result in the project `package.json` and `yarn.lock` + mismatch.__ This will result in the project `package.json` and `yarn.lock` files to be modified, this is normal as this ensures that repeated deployments (with the same config) will have the same config, these changes should not be committed to source control. diff --git a/graph/loaders/users.js b/graph/loaders/users.js index 11d35ac95..cc91af340 100644 --- a/graph/loaders/users.js +++ b/graph/loaders/users.js @@ -11,7 +11,7 @@ const mergeState = (query, state) => { } if (status) { - const { username, banned, suspended } = status; + const { username, banned, suspended, alwaysPremod } = status; if (typeof username !== 'undefined' && username && username.length > 0) { query.merge({ @@ -27,6 +27,12 @@ const mergeState = (query, state) => { }); } + if (typeof alwaysPremod !== 'undefined' && alwaysPremod !== null) { + query.merge({ + 'status.alwaysPremod.status': alwaysPremod, + }); + } + if (typeof suspended !== 'undefined' && suspended !== null) { if (suspended) { query.merge({ diff --git a/graph/mutators/user.js b/graph/mutators/user.js index fa6a18ee5..3732ee0de 100644 --- a/graph/mutators/user.js +++ b/graph/mutators/user.js @@ -10,6 +10,7 @@ const { SET_USERNAME, SET_USER_USERNAME_STATUS, SET_USER_BAN_STATUS, + SET_USER_ALWAYS_PREMOD_STATUS, SET_USER_SUSPENSION_STATUS, UPDATE_USER_ROLES, DELETE_OTHER_USER, @@ -32,6 +33,13 @@ const setUserBanStatus = async (ctx, id, status = false, message = null) => { } }; +const setUserAlwaysPremodStatus = async (ctx, id, status = false) => { + const user = await Users.setAlwaysPremodStatus(id, status, ctx.user.id); + if (user.alwaysPremod) { + ctx.pubsub.publish('userAlwaysPremod', user); + } +}; + const setUserSuspensionStatus = async ( ctx, id, @@ -220,6 +228,7 @@ module.exports = ctx => { setRole: () => Promise.reject(new ErrNotAuthorized()), setUserBanStatus: () => Promise.reject(new ErrNotAuthorized()), setUserSuspensionStatus: () => Promise.reject(new ErrNotAuthorized()), + setUserAlwaysPremodStatus: () => Promise.reject(new ErrNotAuthorized()), setUserUsernameStatus: () => Promise.reject(new ErrNotAuthorized()), setUsername: () => Promise.reject(new ErrNotAuthorized()), stopIgnoringUser: () => Promise.reject(new ErrNotAuthorized()), @@ -256,6 +265,11 @@ module.exports = ctx => { setUserBanStatus(ctx, id, status, message); } + if (ctx.user.can(SET_USER_ALWAYS_PREMOD_STATUS)) { + mutators.User.setUserAlwaysPremodStatus = (id, status) => + setUserAlwaysPremodStatus(ctx, id, status); + } + if (ctx.user.can(SET_USER_SUSPENSION_STATUS)) { mutators.User.setUserSuspensionStatus = (id, until, message) => setUserSuspensionStatus(ctx, id, until, message); diff --git a/graph/resolvers/always_premod_status_history.js b/graph/resolvers/always_premod_status_history.js new file mode 100644 index 000000000..9fccae1d7 --- /dev/null +++ b/graph/resolvers/always_premod_status_history.js @@ -0,0 +1,7 @@ +const { decorateUserField } = require('./util'); + +const AlwaysPremodStatusHistory = {}; + +decorateUserField(AlwaysPremodStatusHistory, 'assigned_by'); + +module.exports = AlwaysPremodStatusHistory; diff --git a/graph/resolvers/index.js b/graph/resolvers/index.js index 4bc9d9624..7dc9d24c8 100644 --- a/graph/resolvers/index.js +++ b/graph/resolvers/index.js @@ -3,6 +3,7 @@ const debug = require('debug')('talk:graph:resolvers'); const Action = require('./action'); const ActionSummary = require('./action_summary'); +const AlwaysPremodStatusHistory = require('./always_premod_status_history'); const Asset = require('./asset'); const AssetActionSummary = require('./asset_action_summary'); const BannedStatusHistory = require('./banned_status_history'); @@ -37,6 +38,7 @@ const plugins = require('../../services/plugins'); let resolvers = { Action, ActionSummary, + AlwaysPremodStatusHistory, Asset, AssetActionSummary, BannedStatusHistory, diff --git a/graph/resolvers/root_mutation.js b/graph/resolvers/root_mutation.js index d2c2965e0..6572cdca2 100644 --- a/graph/resolvers/root_mutation.js +++ b/graph/resolvers/root_mutation.js @@ -74,6 +74,16 @@ const RootMutation = { unbanUser: async (obj, { input: { id } }, { mutators: { User } }) => { await User.setUserBanStatus(id, false); }, + alwaysPremodUser: async (obj, { input: { id } }, { mutators: { User } }) => { + await User.setUserAlwaysPremodStatus(id, true); + }, + removeAlwaysPremodUser: async ( + obj, + { input: { id } }, + { mutators: { User } } + ) => { + await User.setUserAlwaysPremodStatus(id, false); + }, ignoreUser: async (_, { id }, { mutators: { User } }) => { await User.ignoreUser({ id }); }, diff --git a/graph/typeDefs.graphql b/graph/typeDefs.graphql index ebbf8203d..d1f2ca5a5 100644 --- a/graph/typeDefs.graphql +++ b/graph/typeDefs.graphql @@ -142,6 +142,10 @@ input UserStatusInput { # suspended will restrict the returned users to only those that are, or are not # suspended. If not provided, no filtering will be performed. suspended: Boolean + + # alwaysPremod will restrict the returned users to only those that are, or are not + # marked as always premoderate. If not provided, no filtering will be performed. + alwaysPremod: Boolean } type UsernameStatusHistory { @@ -166,6 +170,17 @@ type BannedStatus { history: [BannedStatusHistory!] } +type AlwaysPremodStatusHistory { + status: Boolean! + assigned_by: User + created_at: Date! +} + +type AlwaysPremodStatus { + status: Boolean! + history: [AlwaysPremodStatusHistory!] +} + type SuspensionStatusHistory { until: Date assigned_by: User @@ -184,6 +199,9 @@ type UserStatus { # banned is the bool that determines if the user is banned or not. banned: BannedStatus! + # alwaysPremod is the bool that determines if the user comments are always pushed to the premod queue or not + alwaysPremod: AlwaysPremodStatus! + # suspension is the date that the user is suspended until. suspension: SuspensionStatus! } @@ -1446,6 +1464,22 @@ type UnbanUserResponse implements Response { errors: [UserError!] } +input AlwaysPremodUserInput { + id: ID! +} + +input RemoveAlwaysPremodUserInput { + id: ID! +} + +type AlwaysPremodUserResponse implements Response { + errors: [UserError!] +} + +type RemoveAlwaysPremodUserResponse implements Response { + errors: [UserError!] +} + input SuspendUserInput { id: ID! message: String! @@ -1545,6 +1579,14 @@ type RootMutation { # Mutation is restricted. unsuspendUser(input: UnsuspendUserInput!): UnsuspendUserResponse + # Sets the always premod status on a given user. Requires the `MODERATOR` role. + # Mutation is restricted. + alwaysPremodUser(input: AlwaysPremodUserInput!): AlwaysPremodUserResponse + + # Sets the always premod status on a given user. Requires the `MODERATOR` role. + # Mutation is restricted. + removeAlwaysPremodUser(input: RemoveAlwaysPremodUserInput!): RemoveAlwaysPremodUserResponse + # Sets the ban status on a given user. Requires the `MODERATOR` role. # Mutation is restricted. banUser(input: BanUserInput!): BanUsersResponse diff --git a/locales/en.yml b/locales/en.yml index 02f9876d2..dc5653ed1 100644 --- a/locales/en.yml +++ b/locales/en.yml @@ -3,6 +3,13 @@ en: sort_comments: 'Sort Comments' view_options: 'View Options' already_flagged_username: 'You have already flagged this username.' + alwayspremoddialog: + are_you_sure: 'Are you sure you would like to always premoderate {0}?' + always_premod_user: 'Always premoderate user?' + cancel: Cancel + note: 'Note: {0}' + note_always_premod_user: 'Always premoderating this user will place all of their comments in the Pre-Moderate queue.' + yes_always_premod_user: 'Yes, always premoderate user' bandialog: are_you_sure: 'Are you sure you would like to ban {0}?' ban_user: 'Ban User?' @@ -64,6 +71,7 @@ en: admin: Administrator ads_marketing: 'This looks like an ad/marketing' all: 'All' + always_premod: 'Always premoderated' are_you_sure: 'Are you sure you would like to ban {0}?' ban_user: 'Ban User?' banned: Banned @@ -251,7 +259,7 @@ en: EMAIL_REQUIRED: 'An email address is required' EMAIL_VERIFICATION_TOKEN_INVALID: 'Email verification token is invalid.' INCORRECT_PASSWORD: 'Incorrect Password' - INVALID_ASSET_URL: 'Assert URL is invalid' + INVALID_ASSET_URL: 'Asset URL is invalid' LOGIN_MAXIMUM_EXCEEDED: 'You have made too many unsuccessful password attempts. Please wait.' network_error: 'Failed to connect to server. Check your internet connection and try again.' NO_SPECIAL_CHARACTERS: 'Usernames can contain letters numbers and _ only' @@ -382,6 +390,7 @@ en: actions: Actions all: all all_streams: 'All Streams' + always_premod_user_actions: 'Always Premod User' approve: Approve approved: Approved ban_user: Ban @@ -528,6 +537,8 @@ en: username_flags: 'flags for this username' user_detail: all: All + always_premod: 'Always Premod User' + always_premoded: 'Always premoderated' ban: 'Ban User' banned: Banned email: Email @@ -539,6 +550,7 @@ en: reject_rate: 'Reject Rate' reject_username: 'Reject Username' rejected: Rejected + remove_always_premod: 'Remove Always Premoderate' remove_ban: 'Remove Ban' remove_suspension: 'Remove Suspension' suspend: 'Suspend User' @@ -552,12 +564,14 @@ en: username_rejected: 'Username rejected' user_history: action: Action + always_premod_removed: 'Always premoderate removed' ban_removed: 'Ban removed' date: Date suspended: 'Suspended, {0}' suspension_removed: 'Suspension removed' system: System taken_by: 'Taken By' + user_always_premoded: 'User always premoderated' user_banned: 'User banned' username_status: 'Username {0}' user_impersonating: 'This user is impersonating' diff --git a/locales/es.yml b/locales/es.yml index cff505cbd..83a4632ec 100644 --- a/locales/es.yml +++ b/locales/es.yml @@ -3,26 +3,33 @@ es: sort_comments: 'Ordenar comentarios' view_options: 'Ver opciones' already_flagged_username: 'Usted ya reportó a este usuario.' - bandialog: - are_you_sure: '¿Estás seguro que quieres suspender a {0}?' - ban_user: '¿Quieres suspender al Usuario?' - banned_user: 'Usuario Suspendido' + alwayspremoddialog: + are_you_sure: '¿Estás seguro que quieres pre-moderar a {0}?' + always_premod_user: '¿Pre-moderar al Usuario?' cancel: Cancelar - email_message_ban: "Estimado/a {0},\n\nUsted o alguien con acceso a su cuenta ha violado los lineamientos de nuestra comunidad. Como consecuencia de esto, su cuenta fue bloqueada. No podrá realizar ni reportar más comentarios. Si usted piensa que esto ha sido un error, por favor contáctese con nosotros." note: 'Nota: {0}' - note_ban_user: 'Suspender a este usuario no le va a permitir (al usuario) borrar ni editar ni comentar.' - note_reject_comment: 'Suspender a este usuario también va a colocar este comentario en la cola de Rechazados.' - notify_ban_description: 'Esto notificará al usuario por correo electrónico que fueron suspendidos de la comunidad' - notify_ban_headline: 'Notificar al usuario de la suspensión' + note_always_premod_user: 'Pre-moderar a un usuario pondrá todos sus comentarios en la fila de Pre-Moderación.' + yes_always_premod_user: 'Sí, pre-moderar al usuario.' + bandialog: + are_you_sure: '¿Estás seguro que quieres prohibir a {0}?' + ban_user: '¿Quieres prohibir al Usuario?' + banned_user: 'Usuario prohibido' + cancel: Cancelar + email_message_ban: "Estimado/a {0},\n\nUsted o alguien con acceso a su cuenta ha violado los lineamientos de nuestra comunidad. Como consecuencia de esto, su cuenta fue prohibida. No podrá realizar ni reportar más comentarios. Si usted piensa que esto ha sido un error, por favor contáctese con nosotros." + note: 'Nota: {0}' + note_ban_user: 'Prohibir a este usuario no le va a permitir (al usuario) borrar ni editar ni comentar.' + note_reject_comment: 'Prohibir a este usuario también va a colocar este comentario en la cola de Rechazados.' + notify_ban_description: 'Esto notificará al usuario por correo electrónico que fue prohibido de la comunidad' + notify_ban_headline: 'Notificar al usuario de la prohibición' send: Enviar write_a_message: 'Escribe un mensaje' - yes_ban_user: 'Si, suspender al usuario' + yes_ban_user: 'Si, prohibir al usuario' bio_offensive: 'Esta biografia es ofensiva' cancel: Cancelar characters_remaining: 'caracteres restantes' comment: anon: Anónimo - ban_user: 'Usuario Suspendido' + ban_user: 'Usuario prohibido' comment: 'Publicar un comentario' edited: Editado flagged: Reportado @@ -59,23 +66,24 @@ es: community: account_creation_date: 'Fecha de creación de la cuenta' active: Activa - admin: Administrator + admin: Administrador ads_marketing: 'Esto parece ser un publicidad/acción de comercialización' all: 'Todos' + always_premod: 'Pre-moderado' are_you_sure: '¿Estás seguro que quieres suspender a {0}?' - ban_user: '¿Quieres suspender al Usuario?' - banned: Suspendido - banned_user: 'Usuario suspendido' + ban_user: '¿Quieres prohibir al Usuario?' + banned: Prohibido + banned_user: 'Usuario prohibido' cancel: Cancelar commenter: Comentarista dont_like_username: 'No me gusta este nombre de usuario' - filter_users: 'Filtrarr usuarios' + filter_users: 'Filtrar usuarios' filter_role: Rol flaggedaccounts: 'Nombres de usuario reportados' flags: Reportes impersonating: Impersonando loading: 'Cargando resultados' - moderator: Moderator + moderator: Moderador newsroom_role: 'Rol en la redacción' no_flagged_accounts: 'No hay ningún nombre de usuario reportado en este momento.' no_results: 'No se encontraron usuarios con ese nombre o correo.' @@ -89,7 +97,7 @@ es: status: Estado suspended: Suspendido username_and_email: 'Usuario y Correo' - yes_ban_user: 'Sí, suspendan el usuario' + yes_ban_user: 'Sí, prohiban el usuario' configure: access_message: 'Usted debe ser un administrador para acceder a esta página. Encuentre a otro administrador y actualice los permisos de su cuenta!' apply: Aplicar @@ -200,8 +208,8 @@ es: seconds_plural: segundos email: banned: - body: 'De acuerdo con las guías de comunidad de The Coral Project, su cuenta a sido bloqueada. No podrá hacer comentarios, reportar o entrar en contacto con nuestra comunidad.' - subject: 'Su cuenta ha sido bloqueada' + body: 'De acuerdo con las guías de comunidad de The Coral Project, su cuenta a sido prohibida. No podrá hacer comentarios, reportar o entrar en contacto con nuestra comunidad.' + subject: 'Su cuenta ha sido prohibida' confirm: confirm_email: 'Confirmar Correo' has_been_requested: 'Un correo de confirmación ha sido pedido para la siguiente cuenta:' @@ -283,7 +291,7 @@ es: username_spam: 'Contiene spam' framework: banned_account_body: 'Esto significa que no puedes gustar, marcar o escribir comentarios.' - banned_account_header: 'Tu cuenta se encuentra suspendida.' + banned_account_header: 'Tu cuenta se encuentra prohibida.' changed_name: msg: 'El cambio en su nombre de usuario está siendo revisado por nuestro equipo de moderación.' comment: comentario @@ -355,9 +363,10 @@ es: actions: Acciones all: todos all_streams: 'Todos los Hilos' + always_premod_user_actions: 'Siempre Pre-moderar Usuario' approve: Aprobar approved: Aprobado - ban_user: Bloquear + ban_user: Prohibir billion: B close: Cerrar empty_queue: '¡No hay más comentarios para moderar! Tiempo para un ☕️' @@ -486,13 +495,16 @@ es: username_flags: 'reportes para este nombre de usuario' user_detail: all: Todos - ban: 'Bloquear usuario' - banned: Baneado + always_premod: 'Siempre pre-moderar usuario' + always_premoded: 'Siempre pre-moderado' + ban: 'Prohibir usuario' + banned: Prohibido email: 'Correo electrónico' member_since: 'Miembro desde' reject_rate: 'Promedio de rechazo' rejected: Rechazado - remove_ban: 'Cancelar bloqueo' + remove_always_premod: 'Cancelar siempre pre-moderar' + remove_ban: 'Cancelar prohibición' remove_suspension: 'Cancelar suspensión' suspend: 'Suspender usuario' suspended: Suspendido @@ -503,13 +515,15 @@ es: username_rejected: 'Usuario rechazado' user_history: action: Acción - ban_removed: 'Bloqueo cancelado' + always_premod_removed: 'Siempre pre-moderar cancelado' + ban_removed: 'Prohibición cancelada' date: Fecha suspended: 'Suspendido, {0}' suspension_removed: 'Suspensión cancelada' system: Sistema taken_by: 'Está en uso' - user_banned: 'Usuario bloqueado' + user_always_premoded: 'Usuario siempre pre-moderado' + user_banned: 'Usuario prohibido' username_status: 'Nombre de usuario {0}' user_impersonating: 'Este usuario suplanta a alguien' user_no_comment: 'No has dejado aún ningún comentario. ¡Únete a la conversación!' @@ -523,6 +537,6 @@ es: verify_password: 'La contraseña debe tener por lo menos 8 caracteres' verify_username: 'Los nombres pueden contener letras números y _' view_conversation: 'Ver Conversación' - your_account_has_been_banned: 'Su cuenta ha sido suspendida.' - your_account_has_been_suspended: 'Su cuenta ha sido temporalmente suspendida.' + your_account_has_been_banned: 'Su cuenta ha sido prohibida.' + your_account_has_been_suspended: 'Su cuenta ha sido suspendida.' your_username_has_been_rejected: 'Su cuenta ha sido suspendida porque tu nombre de usuario ha sido considerado no apropiado para el espacio. Para recuperar la cuenta, por favor ingresar un nuevo nombre de usuario.' diff --git a/locales/he.yml b/locales/he.yml new file mode 100644 index 000000000..71e2196ef --- /dev/null +++ b/locales/he.yml @@ -0,0 +1,469 @@ +he: + your_account_has_been_suspended: החשבון שלך הושעה באופן זמני. + your_account_has_been_banned: החשבון שלך הורחק. + your_username_has_been_rejected: החשבון שלך הושעה מפני ששם המשתמש שלך נחשב לא הולם. כדי לשחזר את החשבון שלך, הזן שם משתמש חדש. + embed_comments_tab: תגובות + bandialog: + are_you_sure: "האם אתה בטוח שאתה רוצה לאסור {0}?" + ban_user: "חסום משתמש?" + banned_user: "משתמש מורשה" + cancel: "בטל" + note: "הערה: {0}" + note_reject_comment: "חסימת משתמש זה תציב גם תגובה זו בתור נדחה." + note_ban_user: "חסימת משתמש זה לא תאפשר לו להגיב על תגובות או לדווח עליהן." + yes_ban_user: "כן. חסום משתמש" + write_a_message: "כתוב הודעה" + send: "שלח" + notify_ban_headline: "הודע למשתמש על האיסור" + notify_ban_description: 'פעולה זו תודיע למשתמש באימייל שהם נאסרו מהקהילה' + email_message_ban: "{0},\n\nמישהו עם גישה לחשבון שלך הפר את הנחיות הקהילה שלנו. כתוצאה מכך, החשבון שלך נאסר. לא תוכל עוד להגיב, לדווח או לדווח על תגובות. אם אתה חושב שהדבר נעשה בטעות, צור קשר עם צוות הקהילה שלנו." + bio_offensive: "ביו זה פוגע" + cancel: "בטל" + confirm_email: + click_to_confirm: 'לחץ למטה כדי לאשר את כתובת האימייל שלך' + confirm: "אשר" + password_reset: + mail_sent: 'אם יש לך חשבון רשום, נשלח קישור לאיפוס סיסמה לאותו דאימייל' + set_new_password: "שנה את סיסמתך" + new_password: "סיסמה חדשה" + new_password_help: "הסיסמה חייבת להיות לפחות 8 תווים" + confirm_new_password: "תאשר סיסמא חדשה" + change_password: "שנה סיסמא" + characters_remaining: "תווים נותרים" + comment: + anon: "אנונימי" + undo_reject: "בטל" + ban_user: "חסום משתמש" + comment: "פרסם תגובה" + edited: ערוך + flagged: "מסומנת" + view_context: "הצג הקשר" + comment_box: + post: "שלח" + cancel: "בטל" + reply: "תשיב" + comment: "פרסם תגובה" + name: "שם" + comment_post_notif: "התגובה שלך פורסמה." + comment_post_notif_premod: "תודה על הפרסום. צוות הפיקוח שלנו יבחן את התגובה שלך בקרוב." + comment_post_banned_word: "התגובה שלך מכילה מילה אחת או יותר שאינן מותרות, לכן היא לא תפורסם. אם אתה סבור שהודעה זו שגויה, צור קשר עם צוות הניהול שלנו." + characters_remaining: "תווים נותרים" + comment_history_blank: + info: 'תולדות של התגובות שלך יופיעו כאן' + title: 'לא כתבת שום תגובה' + comment_offensive: "תגובה זו פוגעת" + comment_singular: תגובה + comment_plural: תגובות + comment_post_banned_word: "התגובה שלך מכילה מילה אחת או יותר שאינן מותרות, לכן היא לא תפורסם. אם אתה סבור שהודעה זו שגויה, צור קשר עם צוות הניהול שלנו." + comment_post_notif: "התגובה שלך כבר פורסמה." + comment_post_notif_premod: "תודה על הפרסום. צוות הפיקוח שלנו יבחן את התגובה שלך בקרוב." + common: + copy: 'עותק' + error: 'אירעה שגיאה.' + reply: 'תשובה' + replies: 'תשובות' + reaction: 'ריאקציה' + reactions: 'ריאקציות' + story: 'כתבה' + flagged_usernames: + notify_approved: '{0} אישר את שם המשתמש {1}' + notify_rejected: '{0} דחה את שם המשתמש {1}' + notify_flagged: '{0} דיווח על שם המשתמש {1}' + notify_changed: 'המשתמש {0} שינה את שם המשתמש שלו ל {1}' + community: + account_creation_date: "תאריך היצירה של החשבון" + active: פעיל + admin: מנהל + ads_marketing: "זה נראה כמו מודעה / שיווק" + are_you_sure: "האם אתה בטוח שברצונך לאסור {0}?" + ban_user: "חסום משתמש?" + banned: אסור + banned_user: "משתמש מורשה" + cancel: בטל + dont_like_username: "לא אוהב את שם המשתמש" + flaggedaccounts: "שמות משתמש שדווחו" + flags: דגלים + impersonating: "התחזות" + loading: "טוען תוצאות" + moderator: מנהל + newsroom_role: "תפקיד בחדר החדשות" + no_flagged_accounts: "תור שמות המשתמש המדווחים ריק כעת." + no_results: "לא נמצאו משתמשים בשם המשתמש או בכתובת האימייל הזו. הם מסתתרים!" + offensive: "פוגע" + other: אחר + people: אנשים + role: "בחר תפקיד ..." + select_status: "בחר סטטוס ..." + spam_ads: "ספאם / מודעות" + staff: "צוות" + status: סטטוס + username_and_email: 'שם משתמש ואימייל' + yes_ban_user: "כן. חסום משתמש" + commenter: "מגיב" + configure: + apply: החל + banned_word_text: "הערות המכילות מילים או ביטויים אלה (לא תלויי רישיות) יוסרו באופן אוטומטי מזרם ההערות. הקלד מילה ולחץ על Enter או על Tab כדי להוסיף. אפשר להדביק רשימה מופרדת בפסיקים." + banned_words_title: "רשימת מילים אסורות" + close: "סגור" + close_after: "סגור הערות לאחר" + close_stream: "סגור את זרם ההערות" + close_stream_configuration: "זרם תגובות זה סגור כעת. על ידי פתיחת תגובה זו זרם תגובות חדשות ניתן להגיש ולהציג" + closed_comments_desc: "כתוב הודעה שתוצג כאשר זרם ההערות שלך ייסגר ולא תקבל עוד הערות." + closed_comments_label: "כתוב הודעה ..." + closed_stream_settings: "הודעת זרם סגור" + comment_count_error: "נא הכנס מספר תקף." + comment_count_header: "הגבל אורך תגובה" + comment_count_text_post: תווים + comment_count_text_pre: "תגובות יוגבלו" + comment_settings: הגדרות + comment_stream: "זרם תגובות" + comment_stream_will_close: "זרם ההערות ייסגר" + community: קהילה + configure: הגדר + copy_and_paste: "העתק והדבק קוד למטה לתוך CMS שלך כדי להטביע את תיבת התגובה לכתבות שלך" + custom_css_url: "כתובת האתר של ה- CSS המותאם אישית שלך" + custom_css_url_desc: "כתובת אתר של גיליון סגנונות CSS שתבטל את ברירת המחדל של סגנונות 'הזרמת זרם'. יכול להיות פנימי או חיצוני." + days: ימים + description: "כמנהל, תוכל להתאים אישית את ההגדרות עבור זרם ההערות עבור הסיפור הזה:" + domain_list_text: "הזן את הדומיינים שברצונך להרשות עבור Talk. כלומר, סביבות האחסון הזמני וההפקה המקומית שלך (לדוגמה localhost: 3000, staging.domain.com, domain.com)." + domain_list_title: "דומיינים מותרים" + edit_comment_timeframe_heading: "עריכת מסגרת זמן של תגובה" + edit_comment_timeframe_text_pre: "למשתמשים יהיו" + edit_comment_timeframe_text_post: "שניות כדי לערוך את התגובות שלהם." + embed_comment_stream: "הטמע את הזרם" + enable_premod_links_text: "על המתווכים לאשר כל תגובה המכילה קישור לפני פרסומה." + enable_pre_moderation: "אפשר התנהלות מראש" + enable_pre_moderation_text: "על המנחים לאשר כל הערה לפני פרסומה." + enable_premod_links: "תגובות המכילות קישורים צריך להיות מנוהל מראש" + enable_premod: "אפשר התנהלות מראש" + enable_premod_description: "על המנחים לאשר כל הערה לפני פרסומה." + enable_premod_links_description: "על המתווכים לאשר כל תגובה המכילה קישור לפני פרסומה." + enable_questionbox: "שאל את הקוראים שאלה" + enable_questionbox_description: "שאלה זו תופיע בחלק העליון של זרם תגובות זה. לשאול את הקוראים על בעיה מסוימת במאמר או להציג שאלות דיון וכו '" + hours: שעות + include_comment_stream: "כלול תיאור בזרם תגובות עבור הקוראים" + include_comment_stream_desc: "כתוב הודעה שתופיע בחלק העליון של זרם התגובה שלך. הוסף נושא להנחיות הקהילה וכו '." + include_text: "כלול את הטקסט שלך כאן." + include_question_here: "כתוב את השאלה שלך כאן:" + moderate: מתון + moderation_settings: "הגדרות פיקוח" + open: "פתח" + open_stream: "פתח את הזרם" + open_stream_configuration: "זרם תגובות זה פתוח כעת. על ידי סגירת זרם תגובות זה לא ניתן להגיש תגובות חדשות וכל התגובות הקודמות עדיין יוצגו." + organization_contact_email: "אימייל של הארגון" + require_email_verification: 'דרוש אימות אימייל' + require_email_verification_text: 'משתמשים חדשים חייבים לאמת את האימייל שלהם לפני שתגיב' + save_changes: "שמור שינויים" + shortcuts: קיצורי דרך + sign_out: "התנתק" + stories: כתבות + stream_settings: "הגדרות זרם" + suspect_word_title: "רשימת מילים חשוד" + suspect_word_text: "תגובות שמכילות מילים או ביטויים אלה (לא תלויי רישיות) יודגשו בזרם התגובות. הקלד מילה ולחץ על Enter או על Tab כדי להוסיף. אפשר להדביק רשימה מופרדת בפסיקים." + tech_settings: "הגדרות טכנולוגיה" + title: "הגדר זרם תגובות" + weeks: שבועות + wordlist: "מילים אסורות" + continue: "המשך" + createdisplay: + check_the_form: "טופס לא חוקי. בדוק את השדות" + continue: "המשך עם אותו שם משתמש ב- Facebook" + error_create: "שגיאה בעת שינוי שם המשתמש" + fake_comment_body: "זוהי תגובה לדוגמה. הקוראים יכולים לשתף את מחשבותיהם ואת דעותיהם עם צוות החדשות בקטע התגובות." + fake_comment_date: "לפני 1 דקה" + if_you_dont_change_your_name: "אם לא תשנה את שם המשתמש שלך בשלב זה שם התצוגה שלך ב- Facebook יופיע לצד כל ההערות שלך." + required_field: "שדה נדרש" + save: שמור + special_characters: "שמות משתמש יכולים להכיל מספרי אותיות ו- _ בלבד" + username: שם משתמש + write_your_username: "ערוך את שם המשתמש שלך" + your_username: "שם המשתמש שלך מופיע בכל תגובה שתפרסם." + done: בוצע + edit_comment: + body_input_label: "ערוך תגובה זו" + save_button: "שמור שינויים" + edit_window_expired: "לא ניתן עוד לערוך את ההערה הזו. פרק הזמן לביצוע זה פג. למה לא לכתוב עוד אחד?" + edit_window_expired_close: "סגור" + edit_window_timer_prefix: "פרק זמן זה לעריכה: " + second: "שנייה" + seconds_plural: "שניות" + minute: "דקה" + minutes_plural: "דקות" + email: + suspended: + subject: "חשבונך הושעה" + banned: + subject: "החשבון שלך הורחק" + body: "בהתאם להנחיות הקהילה של פרויקט אלמוגים, החשבון שלך נאסר. כעת אתה מורשה יותר להגיב, לדגל או ליצור מעורבות עם הקהילה שלנו." + confirm: + has_been_requested: 'התבקש אישור באימייל עבור החשבון הבא:' + to_confirm: "כדי לאשר את החשבון, בקר בקישור הבא:" + confirm_email: "אשר אימייל" + if_you_did_not: 'אם לא ביקשת זאת, תוכל להתעלם בבטחה מאימייל זה.' + subject: "אישור דואר אלקטרוני" + password_reset: + we_received_a_request: "קיבלנו בקשה לאפס את הסיסמה שלך. אם לא ביקשת שינוי זה, תוכל להתעלם מהודעת אימייל זו." + if_you_did: "אם עשית," + please_click: "לחץ כאן כדי לאפס את הסיסמה" + embedlink: + copy: "העתק ללוח" + error: + COMMENT_PARENT_NOT_VISIBLE: "התגובה שאתה משיב לה הוסרה או אינה קיימת." + EMAIL_VERIFICATION_TOKEN_INVALID: "אסימון האימייל אינו חוקי." + PASSWORD_RESET_TOKEN_INVALID: "הקישור לאיפוס הסיסמה אינו חוקי." + COMMENT_TOO_SHORT: "התגובות צריכות להיות יותר מתו אחד, אנא שנה את תגובתך ונסה שוב." + NOT_AUTHORIZED: "אינך מורשה לבצע פעולה זו." + NO_SPECIAL_CHARACTERS: "שמות משתמש יכולים להכיל מספרי אותיות ו- _ בלבד" + PASSWORD_LENGTH: "הסיסמה קצרה מדי" + PROFANITY_ERROR: "שמות משתמש לא יכולים להכיל גסות. פנה למנהל המערכת אם אתה סבור שמדובר בטעות." + RATE_LIMIT_EXCEEDED: "חרגת מגבלת התעריף" + USERNAME_IN_USE: "שם משתמש כבר בשימוש" + USERNAME_REQUIRED: "חייב להזין שם משתמש" + EMAIL_NOT_VERIFIED: "כתובת האימייל לא אומתה" + EDIT_WINDOW_ENDED: "לא ניתן עוד לערוך את התגובה הזו. פרק הזמן לביצוע זה פג." + EDIT_USERNAME_NOT_AUTHORIZED: "אין לך הרשאה לעדכן את שם המשתמש שלך." + SAME_USERNAME_PROVIDED: "עליך לשלוח שם משתמש אחר." + EMAIL_IN_USE: 'כתובת האימייל כבר בשימוש' + EMAIL_REQUIRED: 'נדרשת כתובת אימייל' + LOGIN_MAXIMUM_EXCEEDED: "עשית יותר מדי ניסיונות סיסמה לא מוצלחים. המתן בבקשה." + PASSWORD_REQUIRED: "יש להזין סיסמה" + COMMENTING_CLOSED: "התגובה כבר סגורה" + NOT_FOUND: "זרם התגובה כבר סגור" + ALREADY_EXISTS: "המשאב כבר קיים" + INVALID_ASSET_URL: "כתובת האתר של הנכס אינה חוקית" + CANNOT_IGNORE_STAFF: "לא ניתן להתעלם מחברי צוות." + email: "אימייל לא חוקי" + confirm_password: "סיסמאות אינן תואמות. אנא בדוק שוב" + network_error: "נכשל נסיון להתחבר לשרת. בדוק את חיבור האינטרנט שלך ונסה שוב." + email_not_verified: "כתובת האימייל {0} לא אומתה." + email_password: "שילוב דואר אלקטרוני ו / או סיסמה שגוי." + organization_name: "שם הארגון חייב להכיל רק אותיות או מספרים." + password: "הסיסמה צריכה להיות לפחות 8 תווים" + username: "שמות משתמש יכולים להכיל מספרי אותיות ו- _ בלבד" + unexpected: "אירעה שגיאה לא צפויה. מצטער!" + required_field: "שדה חובה" + temporarily_suspended: "החשבון שלך מושעה כעת. הוא יופעל מחדש {0}. אנא צור איתנו קשר אם יש לך שאלות." + flag_comment: "דווח על תגובה" + flag_reason: "סיבה לדיווח (אופציונלי)" + flag_username: "דווח על שם משתמש" + framework: + banned_account_header: "החשבון שלך מוחרם כעת." + banned_account_body: "פירוש הדבר שאינך אוהב, לדווח או לכתוב תגובות." + comment: תגובה + comment_is_ignored: "תגובה זו מוסתרת מפני שהתעלמת ממשתמש זה." + comment_is_rejected: "דחית את התגובה הזו." + comment_is_hidden: "תגובה זו אינה זמינה." + comments: תגובות + configure_stream: "הגדר" + content_not_available: "תוכן זה אינו זמין" + edit_name: + button: שלח + error: "שמות משתמש יכולים להכיל מספרי אותיות ו- _ בלבד" + label: "שם משתמש חדש" + msg: "החשבון שלך מושעה כרגע מפני ששם המשתמש שלך נחשב לא הולם. כדי לשחזר את החשבון שלך, הזן שם משתמש חדש. אנא צור איתנו קשר אם יש לך שאלות." + changed_name: + msg: "שינוי שם המשתמש שלך נמצא בבדיקה על ידי צוות הניהול שלנו." + my_comments: "התגובות שלי" + my_profile: "הפרופיל שלי" + new_count: "הצג {0} עוד {1}" + profile: פרופיל + show_all_comments: "הצג את כל התגובות" + success_bio_update: "הביוגרפיה שלך עודכנה" + success_name_update: "שם המשתמש שלך עודכן" + success_update_settings: "השינויים שביצעת הוחלו על זרם התגובה בכתבה זה" + show_all_replies: הצג את כל התשובות + show_more_replies: הצג תשובות נוספות + view_more_comments: "הצג תגובות נוספות" + view_reply: "הצג תשובה" + from_settings_page: "בדף הפרופיל תוכל לראות את היסטוריית התגובות שלך." + like: לייק + loading_results: "טוען תוצאות" + marketing: "זה נראה כמו מודעה / שיווק" + moderate_this_stream: "נהל את הזרם הזה" + flags: + reasons: + user: + username_offensive: "פוגע" + username_nolike: "לא אוהב" + username_impersonating: "התחזות" + username_spam: "ספאם" + username_other: "אחר" + comment: + comment_offensive: "פוגע" + comment_spam: "ספאם" + comment_noagree: "לא מסכים" + comment_other: "אחר" + suspect_word: "מילה חשודה" + banned_word: "מילה אסורה" + body_count: "גוף התגובה עולה על אורך מרבי" + trust: "אמון" + links: "קישור" + modqueue: + account: "דגלים של החשבון" + actions: פעולות + all: הכל + all_streams: "כל הזרמים" + notify_edited: '{0} ערך תגובה "{1}"' + notify_accepted: '{0} קיבל תגובה "{1}"' + notify_rejected: '{0} דחה תגובה "{1}"' + notify_flagged: '{0} דגל תגובה "{1}"' + notify_reset: '{0} אפס את סטטוס התגובה "{1}"' + approve: "אישרו" + approved: "אושר" + ban_user: "חסום" + billion: ב + close: סגור + empty_queue: "אין תגובות נוספות כדי להתמתן! כולכם תקועים. לכי וליהנות קצת ☕️" + flagged: מסומנת + reported: דיווח + less_detail: "פחות פרטים" + likes: לייקס + million: מ + mod_faster: "מתון מהר יותר עם קיצורי מקשים" + moderate: "מתון ←" + more_detail: "פרטים נוספים" + new: חדש + newest_first: "הכי חדש קודם" + navigation: ניווט + next_comment: "עבור לתגובה הבאה" + toggle_search: "פתח חיפוש" + next_queue: "הפעל תורים" + oldest_first: "הכי עתיק קודם" + premod: מתינות מראש + prev_comment: "עבור לתגובה הקודמת" + reject: "דחה" + rejected: "נדחה" + reply: "תשיב" + select_stream: "בחר זרם" + shift_key: "⇧" + shortcuts: "קיצורי דרך" + sort: "מיין" + show_shortcuts: "הצג קיצורי דרך" + singleview: "מצב זן" + thismenu: "פתח את התפריט" + jump_to_queue: "קפיצה לתור ספציפי" + thousand: k + try_these: "נסה את אלה" + view_more_shortcuts: "הצג קיצורי דרך נוספים" + my_comment_history: "ההיסטוריה של התגובות שלי" + name: שם + no_agree_comment: "אני לא מסכים עם תגובה זו" + no_like_bio: "אני לא אוהב את הביוגרפיה הזאת" + no_like_username: "אני לא אוהב את שם המשתמש הזה" + already_flagged_username: "כבר סימנת שם משתמש זה." + other: אחר + permalink: שתף + personal_info: "תגובה זו מגלה מידע המאפשר זיהוי אישי" + post: שלח + profile: פרופיל + profile_settings: "הגדרות פרופיל" + reply: תשיב + report: דווח + report_notif: "תודה שדיווחת על תגובה זו. צוות ההתייחסות שלנו קיבל הודעה על כך ויבדוק אותו בקרוב." + report_notif_remove: "הדוח שלך הוסר." + reported: דווח + settings: + from_settings_page: "בדף הפרופיל תוכל לראות את היסטוריית התגובות שלך." + my_comment_history: "ההיסטוריה של התגובות שלי" + profile: פרופיל + profile_settings: "הגדרות פרופיל" + sign_in: "היכנס" + to_access: "כדי לגשת לפרופיל" + user_no_comment: "מעולם לא השארת תגובה. הצטרף לשיחה!" + stream: + all_comments: "כל התגובות" + temporarily_suspended: "בהתאם להנחיות הקהילה {0}, חשבונך הושעה באופן זמני. חזור לשיחה {1}." + comment_not_found: "תגובה זו הוסרה או אינה קיימת." + no_comments: "עדיין אין תגובות. האם אתה רוצה לכתוב אחד?" + no_comments_and_closed: "לא היו תגובות על כתבה זה." + step_1_header: "דווח על בעיה" + step_2_header: "עזור לנו להבין" + step_3_header: "תודה על הקלט שלך" + streams: + all: הכל + article: כתבה + closed: סגור + empty_result: "אין נכסים שמתאימים לחיפוש זה. אולי לנסות להרחיב את החיפוש שלך?" + filter_streams: "סינון זרמים" + newest: החדש ביותר + oldest: הוותיק ביותר + open: פתח + pubdate: "תאריך פרסום" + search: לחפש + sort_by: "מיין לפי" + status: "מצב זרם" + stream_status: "מצב זרם" + suspenduser: + title_suspend: "להשעות משתמש" + description_suspend: "אתה משעה את {0}. תגובה זו תעבור לתור נדחה, ו- {0} לא יהיה יכול ללייק, לדווח, להשיב או לפרסם עד להשלמת זמן ההשעיה." + select_duration: "בחר משך השעיה" + one_hour: "1 שעה" + hours: "{0} שעות" + days: "{0} ימים" + cancel: "בטל" + suspend_user: "להשעות משתמש" + email_message_suspend: "{0}, \n\n בהתאם להנחיות הקהילה {1}, חשבונך הושעה באופן זמני. במהלך ההשעיה, לא תוכל להגיב, לדגל או לשתף פעולה עם מפרסמים אחרים. חזור לשיחה {2}." + title_notify: "הודע למשתמש על השעיה זמנית" + notify_suspend_until: "המשתמש {0} הושעה באופן זמני. ההשעיה הזו תסתיים באופן אוטומטי {1}." + description_notify: "השעיית משתמש זה תשבית זמנית את החשבון שלו." + write_message: "לכתוב הודעה" + send: שלח + reject_username: + username: שם משתמש + no_cancel: "אין ביטול" + description_reject: "האם אתה רוצה לאסור את המשתמש הזה באופן זמני בגלל {0} שלו? פעולה זו תשעה את המשתמש הזה באופן זמני עד שיכתבו מחדש את {0}." + title_notify: "הודע למשתמש על השעיה זמנית" + description_notify: "השעיית משתמש זה תשבית זמנית את החשבון שלו." + title_reject: "שמנו לב שדחית שם משתמש" + suspend_user: "להשעות משתמש" + yes_suspend: "כן, להשעות" + email_message_reject: 'לאחרונה חבר בקהילה שלנו סימן את שם המשתמש שלך לבדיקה. בגלל התוכן של שם המשתמש שלך, שם המשתמש שלך נדחה. פירוש הדבר שאינך יכול עוד להגיב או לסמן תוכן עד שתשנה את שם המשתמש שלך. אנא שלח לנו אימייל אם יש לך שאלות או חששות.' + write_message: "לכתוב הודעה" + send: שלח + thank_you: "אנו מעריכים את בטיחותך ומשובך. מנחה יסקור את הדוח שלך." + user: + bio_flags: "דגלים עבור ביוגרפיה זו" + user_bio: "ביוגרפיה של המשתמש" + username_flags: "דגלים עבור שם משתמש זה" + user_detail: + remove_suspension: "הסר השעיה" + suspend: "להשעות משתמש" + remove_ban: "הסרת חסימה" + ban: "לחסום משתמש" + member_since: "חבר מאז" + email: 'אימייל' + total_comments: "סך הכל תגובות" + reject_rate: "שיעור דחייה" + reports: "דיווחים" + all: "כל" + rejected: "נדחה" + account_history: "היסטוריית החשבון" + user_impersonating: "משתמש זה מתחזה" + user_no_comment: "מעולם לא השארת תגובה. הצטרף לשיחה!" + username_offensive: "שם משתמש זה פוגע" + view_conversation: "הצג שיחה" + install: + initial: + description: "בואו נגדיר את קהילת Talk שלך בכמה שלבים קצרים בלבד." + submit: "תחל" + add_organization: + description: 'ספר לנו את שם הארגון שלך. פעולה זו תופיע באימייל בעת הזמנת חברי צוות חדשים.' + label: "שם ארגון" + save: "שמור" + create: + email: 'כתובת אימייל' + username: "שם משתמש" + password: "סיסמה" + confirm_password: "אשר סיסמה" + save: "שמור" + permitted_domains: + title: "דומיינים מותרים" + description: "הזן את הדומיינים שברצונך להרשות עבור Talk. כלומר, סביבות האחסון הזמני וההפקה המקומית שלך (לדוגמה localhost: 3000, staging.domain.com, domain.com)." + submit: "סיים את ההתקנה" + final: + description: "תודה שהתקנת את Talk! שלחנו הודעת אימייל לאימות כתובת האימייל שלך. בזמן שתסיים להגדיר את החשבון, תוכל להתחיל לעסוק עם הקוראים שלך כעת." + launch: "הפעל את Talk" + close: "סגור את המתקין" + admin_sidebar: + view_options: "הצג אפשרויות" + sort_comments: "מיין תגובות" diff --git a/locales/sr.yml b/locales/sr.yml new file mode 100644 index 000000000..c04b60413 --- /dev/null +++ b/locales/sr.yml @@ -0,0 +1,576 @@ +sr: + admin_sidebar: + sort_comments: 'Sortiranje komentara' + view_options: 'Opcije pregleda' + already_flagged_username: 'Već ste obilježili ovog korisnika' + bandialog: + are_you_sure: 'Jeste li sigurni da želite da banujete {0}?' + ban_user: 'Banuj korisnika' + banned_user: 'Banovan korisnik' + cancel: 'Otkaži' + email_message_ban: 'Poštovani {0},\n\nneko sa pristupom vašem nalogu je prekršio pravila komentarisanja. Zbog toga smo bili primorani da banujemo vaš nalog i više nećete moći da komentarišete, reagujete i prijavljujete komentare. Ukoliko smatrate da je došlo do greške, molim vas da kontaktirate naš tim' + note: 'Napomena: {0}' + note_ban_user: 'Banovanje ovog korisnika će onemogućiti komentarisanje, prijavljivanje i reagovanje na komentare.' + note_reject_comment: 'Banovanje ovog korisnika će staviti ovaj komentar na listu odbijeno' + notify_ban_description: 'Ovo će obavijestit korisnika putem e-maila da je njegov/njen nalog banovan' + notify_ban_headline: 'Obavijesti korisnika o banovanju' + send: 'Pošalji' + write_a_message: 'Napiši poruku' + yes_ban_user: 'Banuj koriniska' + bio_offensive: 'Ova biografija je neprimjerenog sadržaja.' + cancel: 'Otkaži' + characters_remaining: 'Preostali znakovi' + comment: + anon: 'Anoniman' + ban_user: 'Banuj korisnika' + comment: 'Postavi komentar' + edited: 'Izmijenjeno' + flagged: 'Obilježeno' + undo_reject: 'Prethodni korak' + view_context: 'Vidi kontekst' + comment_box: + cancel: 'Otkaži' + characters_remaining: 'Preostali znakovi' + comment: 'Postavi komentar' + comment_post_banned_word: 'Vaš komentar sadrži jednu ili više riječi koje nisu dozvoljene i neće biti objavljen. Ukoliko smatrate nije tako, molim vas kontaktirajte naš tim moderatora.' + comment_post_notif: 'Vaš komentar je postavljen' + comment_post_notif_premod: 'Hvala na objavi. Naš tim moderatora će ubrzo pregledati vaš komentar' + name: 'Ime' + post: 'Objava' + reply: 'Odgovori' + comment_history_blank: + info: 'Istorija vaših komentara će se prikazati ovdje' + title: 'Niste postavili nijedan komentar' + comment_offensive: 'Neprimjereno' + comment_plural: 'Komentari' + comment_post_banned_word: 'Vaš komentar sadrži jednu ili više riječi koje nisu dozvoljene i neće biti objavljen. Ukoliko smatrate nije tako, molim vas kontaktirajte naš tim moderatora.' + comment_post_notif: 'Vaš komentar je postavljen' + comment_post_notif_premod: 'Hvala na objavi. Naš tim moderatora će ubrzo pregledati vaš komentar' + comment_singular: 'Komentar' + common: + contains_link: 'Sadrži link' + copy: 'Kopiraj na tablu' + copied: 'Kopirano' + notsupported: 'Nije podržano' + error: 'Došlo je do greške' + reaction: 'Reakcija' + reactions: 'Reakcije' + reply: 'Odgovori' + replies: 'Odgovori' + story: 'Priča' + community: + account_creation_date: 'Datum kreiranja naloga' + active: 'Aktivan' + admin: 'Administrator' + ads_marketing: 'Ovo izgleda kao reklama' + all: 'Sve' + are_you_sure: 'Jeste li sigurni da želite da banujete {0}?' + ban_user: 'Banuj korisnika' + banned: 'Banovan' + banned_user: 'Banovan korisnik' + cancel: 'Otkaži' + commenter: 'Komentator' + dont_like_username: 'Ne sviđa mi se korisničko ime' + filter_users: 'Segmentiraj korisnike' + filter_role: 'Uloga' + flaggedaccounts: 'Prijavljena korisnička imena' + flags: 'Prijave' + impersonating: 'Korisnik se lažno predstavlja' + loading: 'Učitavanje rezultata' + moderator: 'Moderator' + newsroom_role: 'Uloga u redakciji' + no_flagged_accounts: 'Nema prijavljenih korisničkih naloga' + no_results: 'Nema rezultata' + offensive: 'Korisničko ime je neprimjereno' + other: 'Drugo' + people: 'Ljudi' + role: 'Izaberi ulogu' + select_status: 'Izaberi status' + spam_ads: 'Reklame' + staff: 'Član redakcije' + status: 'Status rasprave' + suspended: 'Suspendovan' + username_and_email: 'Korisničko ime i e-mail' + yes_ban_user: 'Banuj koriniska' + configure: + access_message: 'Morate biti administrator kako biste pristupili podešavanjima. Zamolite administratora da vam da tu ulogu' + apply: 'Primijeni' + banned_word_text: 'Komentari koji sadrže ove riječi ili fraze (a nijesu osjetljivi na veličinu slova) automatski će biti uklonjeni iz komentara. Unesite riječ i pritisnite Enter ili Tab da dodate. Opciono stavite listu sa odvojenu zarezima' + banned_words_title: 'Lista zabranjenih riječi' + close: 'Zatvori instalaciju' + close_after: 'Zatvori komentare poslije' + close_stream: 'Zatvori raspravu' + close_stream_configuration: 'Ova rasprava je trenutno zatvorena. Otvaranjem ove rasprave novi komentari mogu biti postavljani i prikazivani' + closed_comments_desc: 'Napišite poruku koja će se prikazati kada zatvorite vašu raspravu i kada ne prihvatate dalje komentare.' + closed_comments_label: 'Napiši poruku' + closed_stream_settings: 'Poruka za zatvaranje rasprave' + comment_count_error: 'Unesite validan broj' + comment_count_header: 'Ograniči dužinu komentara' + comment_count_text_post: 'Karakteri' + comment_count_text_pre: 'Komentari će biti ograničeni na' + comment_settings: 'Podešavanja' + comment_stream: 'Rasprava' + comment_stream_will_close: 'Rasprava će se zatvoriti' + community: 'Zajednica' + configure: 'Podesite' + copy_and_paste: 'Kopirajte i zalijepite ispod u CMS' + custom_css_url: 'Prilagođen CSS URL' + custom_css_url_desc: 'URL od CSS-a koji će da prepravi standardni stil ugrađivanja. Može biti interni ili eksterni?' + days: 'Dani' + description: 'Hvala što ste instalirali Talk! Poslali smo vam e-mail kako biste povrdili vašu e-mail adresu. Dok završavate podešavanje naloga, možete početi da komunicirate sa čitaocima.' + disable_commenting_desc: 'Napišite poruku koja će se prikazivati kada su komentari deaktivirani.' + disable_commenting_title: 'Deaktiviraj komentare na čitavom sajtu' + domain_list_text: 'Unesite domene koje biste želeli da dozvolite za Talk npr. vaše lokalno okruženje za postavljanje i proizvodnju (npr. localhost 3000 staging.domain.com domain.com)?' + domain_list_title: 'Dozvoljeni domeni' + edit_comment_timeframe_heading: 'Uredi vremenski okvir komentara' + edit_comment_timeframe_text_post: 'Sekunde za uređenje njihovih komentara' + edit_comment_timeframe_text_pre: 'Komentatori će imati' + edit_info: 'Uredi informacije' + embed_comment_stream: 'Ugradi raspravu' + enable_pre_moderation: 'Omogući moderiranje prije objave' + enable_pre_moderation_text: 'Moderatori moraju odobriti svaki komentar prije nego je objavljen' + enable_premod: 'Omogući moderiranje prije objave' + enable_premod_description: 'Moderatori moraju odobriti svaki komentar prije nego je objavljen' + enable_premod_links: 'Moderiraj komentare sa linkovima prije objave' + enable_premod_links_description: 'Moderatori moraju odobriti svaki komentar sa linkom prije nego je objavljen' + enable_premod_links_text: 'Moderatori moraju odobriti svaki komentar sa linkom prije nego je objavljen' + enable_questionbox: 'Pitaj čitaoce' + enable_questionbox_description: 'Ovo pitanje će se pojaviti na vrhu rasprave. Pitajte čitaoce o određenoj temi u članku ili postavite pitanja za diskusiju itd' + hours: 'Sati' + code_of_conduct_summary: 'Rezime Kodeksa Ponašanja' + code_of_conduct_summary_desc: 'Ova poruka će se pojaviti iznad svake rasprave na vašem sajtu. Kliknite ovdje da saznate više o pisanju dobrog koda.' + include_question_here: 'Napišite pitanje ovdje' + include_text: 'Ubacite tekst ovdje' + moderate: 'Moderiraj' + moderation_settings: 'Podešavanja moderacije' + open: 'Otvori' + open_stream: 'Otvori raspravu' + open_stream_configuration: 'Ovaj stream komentara trenutačno je otvoren. Zatvaranjem ovog komentarskog streama ne mogu se poslati novi komentari, a svi prethodni komentari i dalje će biti prikazani.' + organization_contact_email: 'Email organizacije nije validan' + organization_info_copy: 'Ove informacije se koriste u e-mailovima koje generiše Talk. Ovo povezuje poruke sa vašom organizacijom i pruža mogućnost korisnicima da vas kontaktiraju ako imaju problem sa svojim nalogom.' + organization_info_copy_2: 'Preporučujemo kreiranje generičkog email naloga (organizacija@organizacija.me) za ovu svrhu. Ovo znači da nalog može da traje a da ne izlaže konkretna imena ljudi koja bi bila podložna targetiranju korisnika ukoliko je njihov nalog blokian' + organization_information: 'Informacije o organizaciji' + organization_name: 'Ime organizacije može sadržati brojeve ili slova' + suspect_or_forbidden_words_placeholder: 'Riječ ili fraza' + product_guide_link: 'Priručnik' + report_bug_or_feedback: 'Prijavi grešku ili pošalji povratnu informaciju' + require_email_verification: 'Zahtijeva e-mail verifikaciju' + require_email_verification_text: 'Novi korisnici moraju verifikovati svoj e-mail prije mogućnosti postavljanja komentara' + save: 'Sačuvaj' + save_changes: 'Sačuvaj izmjene' + save_changes_dialog: + cancel: 'Otkaži' + copy: 'Kopiraj na tablu' + discard: 'Izbriši/Odbaci' + save_settings: 'Sačuvaj podešavanja' + unsaved_changes: 'Nesačuvane promjene' + shortcuts: 'Prečice' + sign_out: 'Odjavi se' + stories: 'Priče' + stream_settings: 'Podešavanje rasprave' + suspect_word_text: 'Komentari koji sadrže ove riječi ili fraze (a nijesu osjetljivi na veličinu slova) automatski će biti uklonjeni iz komentara. Unesite riječ i pritisnite Enter ili Tab da dodate. Opciono stavite listu sa odvojenu zarezima' + suspect_word_title: 'Lista sumnjivih riječi' + tech_settings: 'Tehnička podešavanja' + title: 'Niste postavili nijedan komentar' + view_last_version: 'Vidi poslednju verziju' + weeks: 'Nedelje' + wordlist: 'Banovane riječi' + confirm_email: + click_to_confirm: 'Kliknite ispod da potvrdite Vašu e-mail adresu' + confirm: 'Potvrdi' + email_confirmation: 'E-mail potvrda' + continue: 'Nastavi' + createdisplay: + check_the_form: 'Nevažeća forma. Molim vas provjerite polja' + continue: 'Nastavi' + error_create: 'Greška u promjeni korisničkog imena' + fake_comment_body: 'Ovo je primjer komentara. Čitaoci mogu da podijele stavove i komuniciraju sa redakcijom u sekciji komentara' + fake_comment_date: 'Prije minut' + if_you_dont_change_your_name: 'Ako ne promijenite svoje korisničko ime na ovom koraku, vaše ime na Facebook-u će se pojaviti zajedno sa svim Vašim komentarima' + required_field: 'Obavezno polje' + save: 'Sačuvaj' + special_characters: 'Korisničko ime može da sadrži slova, brojeve i' + username: 'Korisničko ime mora sadržati slova, brojeve I _ samo' + write_your_username: 'Izmijeni svoje korisničko ime' + your_username: 'Vaše korisničko ime se pojavljuje u svakom komentaru koji napišete' + done: 'Završeno' + edit_comment: + body_input_label: 'Izmijeni komentar' + edit_window_expired: 'Vrijeme je isteklo i ovaj komentar se više ne može izmijeniti. Zašto ne postavite novi komentar?' + edit_window_expired_close: 'Zatvori' + edit_window_timer_prefix: 'Uredi prozor' + minute: 'Minut' + minutes_plural: 'Minuti' + save_button: 'Sačuvaj izmjene' + second: 'Sekund' + seconds_plural: 'Sekunde' + email: + banned: + body: 'Svaš nalog je banovan. Više vam nije dozvoljeno da komentarišete, prijavljujete i budete dio zajednice' + subject: 'Potvrda e-maila' + confirm: + confirm_email: 'Email adrese se ne podudaraju, probajte ponovo' + has_been_requested: 'Potvrda e-mail adrese je neophodna za ovaj nalog' + if_you_did_not: 'Ukoliko niste ovo zahtijevali, ignorišite ovaj e mail.' + subject: 'Potvrda e-maila' + to_confirm: 'Da aktivirate nalog, posjetite sljedeći link:' + password_change: + body: 'Lozinka za vaš nalog je promjenjena.\n\nUkoliko niste vi zatražili ovu promjenu, molimo kontaktirajte nas {0}.' + subject: '{0} promena lozinke' + password_reset: + if_you_did: 'Ako jeste' + please_click: 'Kliknite ovdje da obnovite šifru' + subject: 'Potvrda e-maila' + we_received_a_request: 'Primili smo vaš zahtjev za obnovu šifre. Ukoliko niste tražili ovu promjenu, ignorišite ovaj mail.' + suspended: + subject: 'Potvrda e-maila' + embed_comments_tab: 'Komentari' + embedlink: + copy: 'Kopiraj na tablu' + copied: 'Kopirano' + error: + ALREADY_EXISTS: 'Resurs već postoji' + AUTHENTICATION: 'Došlo je do greške prilikom potvrde vašeg naloga.' + CANNOT_IGNORE_STAFF: 'Ne možete ignorisati članove redakcije.' + COMMENT_PARENT_NOT_VISIBLE: 'Komentar na koji odgovarate je uklonjen ili ne postoji' + COMMENT_TOO_SHORT: 'Komentar mora sadržati više od jednog znaka. Molimo vas izmijenite vaš komentar i pokušajte ponovo' + COMMENT_TOO_LONG: 'Komentar je predugačak' + COMMENTING_CLOSED: 'Komentarisanje je zatvoreno' + COMMENTING_DISABLED: 'Komentarisanje trenutno nije moguće na sajtu.' + confirm_password: 'Potvrdi šifru' + DELETION_NOT_SCHEDULED: 'Brisanje nije zakazano.' + EDIT_USERNAME_NOT_AUTHORIZED: 'Nemate dozvolu da izmijenite vaše korisiničko ime.' + EDIT_WINDOW_ENDED: 'Ne možete izmijeniti komentar, vrijeme je isteklo' + email: 'Molimo Vas unesite validan e-mail' + EMAIL_ALREADY_VERIFIED: 'E-mail adresa je već potvrđena.' + EMAIL_IN_USE: 'E-mail adresa je već u upotrebi' + email_not_verified: 'E-mail adresa nije potvrđena.' + EMAIL_NOT_VERIFIED: 'E-mail adresa nije potvrđena.' + email_password: 'E-mail i/ili šifra se ne podudaraju' + EMAIL_REQUIRED: 'Obavezna e-mail adresa' + EMAIL_VERIFICATION_TOKEN_INVALID: 'Šifra potvrde maila nije validna' + INCORRECT_PASSWORD: 'Pogrešna šifra' + INVALID_ASSET_URL: 'Istaknuti URL nije validan' + LOGIN_MAXIMUM_EXCEEDED: 'Ukucali ste pogrešnu šifru previše puta. Molimo sačekajte.' + network_error: 'Nemoguće povezivanje sa serverom. Provjerite konekciju i pokušajte ponovo' + NO_SPECIAL_CHARACTERS: 'Korisničko ime može samo sadržati slova, brojeve i _' + NOT_AUTHORIZED: 'Nemate dozvolu da sprovedete akciju' + NOT_FOUND: 'Podatak nije pronađen' + organization_contact_email: 'Email organizacije nije validan' + organization_name: 'Ime organizacije može sadržati brojeve ili slova' + password: 'Šifra mora sadržati minimum 8 karaktera' + PAGE_NOT_AVAILABLE_ROLE: 'Ovu stranicu mogu koristiti samo članovi redakcije. Molimo kontaktirajte administratora ukoliko želite da se priključite timu' + PASSWORD_INCORRECT: 'Pogrešno ste unijeli šifru' + PASSWORD_LENGTH: 'Šifra je prekratka' + PASSWORD_REQUIRED: 'Obavezno unijeti šifru' + PASSWORD_RESET_TOKEN_INVALID: 'Link za resetovanje šifre je nevažeći' + PROFANITY_ERROR: 'Korisničko ime ne može sadržati psovke. Kontaktirajte administratora ukoliko smatrate da je u pitanju greška' + RATE_LIMIT_EXCEEDED: 'Premašeno je ograničenje' + required_field: 'Obavezno polje' + SAME_USERNAME_PROVIDED: 'Morate izabrati drugo korisničko ime' + temporarily_suspended: 'U skladu sa pravilima naše zajednice, vaš nalog je privremeno suspendovan. Molimo vas da se ponovo uključite u razgovor.' + unexpected: 'Došlo je do greške. Izvinite.' + username: 'Korisničko ime mora sadržati isključivo slova, brojeve i _' + USERNAME_IN_USE: 'Korisničko ime je već u upotrebi' + USERNAME_REQUIRED: 'Morate unijeti korisničko ime' + flag_comment: 'Prijavi komentar' + flag_reason: 'Razlog prijave (opciono)' + flag_reasons: + username: + impersonating: 'Korisnik se lažno predstavlja' + nolike: 'Ne dopada mi se korisničko ime' + offensive: 'Korisničko ime je neprimjereno' + other: 'Drugo' + spam: 'Ovo izgleda kao reklama/marketing' + flag_username: 'Prijavi korisničko ime' + flagged_usernames: + notify_approved: '{0} je odobrio korisničko ime {1}' + notify_changed: 'Korisnik {0} je promijenio korisničko ime u {1}' + notify_flagged: '{0} je prijavio korisničko ime {1}' + notify_rejected: '{0} je odbio korisničko ime {1}' + flags: + reasons: + comment: + banned_word: 'Banovana riječ' + comment_noagree: 'Nisam saglasan' + comment_offensive: 'Neprimjereno' + comment_other: 'drugo' + comment_spam: 'spam' + links: 'link' + suspect_word: 'Neprimjerena riječ' + trust: 'karma' + user: + username_impersonating: 'Lažno predstavljanje' + username_nolike: 'ne dopada mi se' + username_offensive: 'Korisničko ime je neprimjereno.' + username_other: 'drugo' + username_spam: 'spam' + framework: + banned_account_body: 'To znači da ne možete reagovati, prijavljivati ili pisati komentare' + banned_account_header: 'Vaš nalog je trenutno banovan' + changed_name: + msg: 'Izmjena vašeg korisničkog imena prolazi provjeru našeg tima moderatora' + comment: 'Postavi komentar' + comment_is_deleted: 'Korisnik je izbrisao nalog' + comment_is_hidden: 'Komentar nije dostupan' + comment_is_ignored: 'Komentar je skriven, jer ste ignorisali korisnika' + comment_is_rejected: 'Odbili ste komentar' + comments: 'Komentari' + configure_stream: 'Podesi' + content_not_available: 'Sadržaj nije dostupan' + edit: 'Izmijeni' + edit_name: + button: 'Podnesi' + error: 'Došlo je do greške' + label: 'Novo korisničko ime' + msg: 'Izmjena vašeg korisničkog imena prolazi provjeru našeg tima moderatora' + my_comments: 'Moji komentari' + my_profile: 'Moj profil' + new_count: 'Vidi još {0} / {1}' + profile: 'Nalog' + show_all_comments: 'Prikaži sve komentare' + show_all_replies: 'Prikaži sve odgovore' + show_more_replies: 'Prikaži još odgovora' + success_bio_update: 'Vaša biografija je izmijenjena' + success_name_update: 'Vaše korisničko ime je izmijenjeno' + success_update_settings: 'Promjene koje ste unijeli su primijenjene na raspravu na ovom članku.' + view_more_comments: 'Vidi još komentara' + view_reply: 'vidi odgovor' + from_settings_page: 'Na profilu možete vidjeti istoriju komentara' + install: + add_organization: + description: 'Hvala što ste instalirali Talk! Poslali smo vam e-mail kako biste povrdili vašu e-mail adresu. Dok završavate podešavanje naloga, možete početi da komunicirate sa čitaocima.' + label: 'Novo korisničko ime' + save: 'Sačuvaj' + create: + confirm_password: 'Potvrdi šifru' + email: 'Molimo Vas unesite validan e-mail' + organization_contact_email: 'Email organizacije nije validan' + password: 'Šifra mora sadržati minimum 8 karaktera' + save: 'Sačuvaj' + username: 'Korisničko ime mora sadržati isključivo slova, brojeve i _' + final: + close: 'Zatvori instalaciju' + description: 'Hvala što ste instalirali Talk! Poslali smo vam e-mail kako biste povrdili vašu e-mail adresu. Dok završavate podešavanje naloga, možete početi da komunicirate sa čitaocima.' + launch: 'Pokreni Talk' + initial: + description: 'Hvala što ste instalirali Talk! Poslali smo vam e-mail kako biste povrdili vašu e-mail adresu. Dok završavate podešavanje naloga, možete početi da komunicirate sa čitaocima.' + submit: 'Počni' + permitted_domains: + description: 'Hvala što ste instalirali Talk! Poslali smo vam e-mail kako biste povrdili vašu e-mail adresu. Dok završavate podešavanje naloga, možete početi da komunicirate sa čitaocima.' + submit: 'Počni' + title: 'Niste postavili nijedan komentar' + like: 'Dopada mi se' + loading_results: 'rezultati se učitavaju' + login: + email_address: 'e-mail adresa' + forgot_password: 'Zaboravili ste šifru?' + go_back: 'Vrati se' + sign_in: 'Prijavi se' + sign_in_button: 'Dugme za prijavu' + sign_in_message: 'Prijavi se kako bi komunikacirao sa zajednicom' + password: 'Šifra mora sadržati minimum 8 karaktera' + reset_password_send_button: 'povrati šifru' + request_passowrd: 'Zahtijevaj novu šifru' + team_sign_in: 'Prijava tima' + marketing: 'Ovo izgleda kao reklama/marketing' + moderate_all_streams: 'Moderiraj komentare na svim pričama' + moderate_this_stream: 'Moderiraju ovu raspravu' + modqueue: + account: 'Prijave naloga' + actions: 'akcije' + all: 'Sve' + all_streams: 'Sve rasprave' + approve: 'Odobri' + approved: 'Odobreno' + ban_user: 'Banuj korisnika' + ban_user_actions: 'Banuj korisničke akcije' + billion: 'Milijardi' + close: 'Zatvori instalaciju' + empty_queue: 'Nema više komentara za moderiranje.' + flagged: 'Obilježeno' + jump_to_queue: 'Preskoči na red' + less_detail: 'Manje detalja' + likes: 'Sviđanja' + million: 'milion' + mod_faster: 'Moderiraj brže uz prečice na tastaturi' + moderate: 'Moderiraj' + more_detail: 'Više detalja' + navigation: 'navigacija' + new: 'novo' + newest_first: 'Prvo najnoviji' + next_comment: 'Idi na sljedeći komentar' + next_queue: 'Preskoči red' + notify_accepted: '{0} je prihvatio komentar "{1}"' + notify_edited: '{0} je izmjenio komentar "{1}"' + notify_flagged: '{0} je označio komentar "{1}"' + notify_rejected: '{0} je odbio komentar "{1}"' + notify_reset: '{0} je resetovao status komentara "{1}"' + oldest_first: 'Prvo najstariji' + premod: 'mod pre-moderacija' + prev_comment: 'idi na prethodni komentar' + reject: 'Odbij' + rejected: 'Odbijen' + reply: 'Odgovori' + reported: 'Prijavljen' + select_stream: 'Odaberi raspravu' + shift_key: '⇧' + shortcuts: 'Prečice' + show_shortcuts: 'Prikaži prečice' + singleview: 'Zen stanje' + sort: 'Sortiraj' + suspend: 'Suspenduj korisnika' + system_withheld: 'Zadržan u sistemu' + thismenu: 'Otvori meni' + thousand: 'hiljada' + toggle_search: 'Otvori pretragu' + try_these: 'Pokušaj ovo' + view_more_shortcuts: 'Vidi dodatne prečice' + my_comment_history: 'Istorija komentara' + name: 'Ime' + no_agree_comment: 'Nisam saglasan sa ovim komentarom' + no_like_bio: 'Ne dopada mi se ova biografija' + no_like_username: 'Ne dopada mi se ovo korisničko ime' + other: 'Drugo' + password_reset: + change_password: 'Promijeni šifru' + change_password_help: 'Unesite novu šifru za prijavu. Vodite računa da je šifra bezbjedna' + confirm_new_password: 'Potvrdite novu šifru' + mail_sent: 'Ukoliko imate registrovan nalog, link za resetovanje šifre je poslat na email' + new_password: 'Nova šifra' + new_password_help: 'Šifra mora sadržati minimum 8 znakova' + set_new_password: 'Promijeni šifru' + permalink: 'Podijeli' + personal_info: 'Ovaj komentar sadrži lične informacije' + post: 'Objava' + profile: 'Nalog' + profile_settings: 'Podešavanja naloga' + reject_username: + description_notify: 'Suspendovanje korisnika će im privremeno onemogućiti nalog' + description_reject: 'Da li želite da privremeno banujete ovog korisnika? Ukoliko to učinite, korisnik će biti suspendovan dok ne izmijeni' + email_message_reject: 'Član zajednice je zatražio provjeru vašeg korisničkog imena. Zbog tog sadržaja je vaše korisničko ime odbijeno. To znači da ne možete komentarisati, označavati i reagovati na komentare dok ne promijenite korisničko ime. Molimo da pošaljete e-mail ako imate komentare ili primjedbe.' + no_cancel: 'Ne poništavaj' + send: 'Pošalji' + suspend_user: 'Suspenduj korisnika' + title_notify: 'Obavijesti korisnika o privremenoj suspenziji' + title_reject: 'Primijetili smo da ste odbili korisničko ime' + username: 'Korisničko ime mora sadržati slova, brojeve I _ samo' + write_message: 'Napiši poruku' + yes_suspend: 'Potvrdi suspenziju' + reject_username_dialog: + cancel: 'Otkaži' + description: 'Hvala što ste instalirali Talk! Poslali smo vam e-mail kako biste povrdili vašu e-mail adresu. Dok završavate podešavanje naloga, možete početi da komunicirate sa čitaocima.' + message: 'Razlog prijave (opciono)' + reason: 'Razlog' + reject_username: 'Odbij korisničko ime' + title: 'Niste postavili nijedan komentar' + reply: 'Odgovori' + report: 'Prijavi' + report_notif: 'Hvala što ste prijavili komentar. Naš tim je obaviješten i provjera će biti izvršena brzo.' + report_notif_remove: 'Vaša prijava je uklonjena' + reported: 'Prijavljen' + settings: + from_settings_page: 'Na profilu možete vidjeti istoriju komentara' + my_comment_history: 'Istorija komentara' + profile: 'Nalog' + profile_settings: 'Podešavanja naloga' + sign_in: 'Prijavi se' + to_access: 'Pristupi nalogu' + user_no_comment: 'Nikada niste napisali komentar. Priključite se raspravi.' + step_1_header: 'Prijavi problem' + step_2_header: 'Pomozite nam da razumijemo' + step_3_header: 'Hvala na informaciji' + stream: + all_comments: 'Svi komentari' + comment_not_found: 'Komentar je uklonjen ili ne postoji' + no_comments: 'Još uvijek nema komentara. Zašto ne napišete nešto?' + no_comments_and_closed: 'Nema komenatara za ovaj članak' + temporarily_suspended: 'U skladu sa pravilima naše zajednice, vaš nalog je privremeno suspendovan. Molimo vas da se ponovo uključite u razgovor.' + streams: + all: 'Sve' + article: 'Priča' + closed: 'Zatvoreno' + empty_result: 'Nema rezultata pretrage. Pokušajte da proširite pretragu.' + filter_streams: 'Filteruj rasprave' + most_recent_stories: 'Najnovije priče' + newest: 'Najnovije' + no_results: 'Nema rezultata' + oldest: 'Najstarije' + open: 'Otvori' + pubdate: 'Datum objave' + search: 'Pretraga' + search_results: 'Rezultati pretrage' + sort_by: 'Sortiraj po' + status: 'Status rasprave' + stream_status: 'Status rasprave' + suspenduser: + cancel: 'Otkaži' + day: '{0} dana' + days: '{0} dana' + description_notify: 'Suspendovanje korisnika će im privremeno onemogućiti nalog' + description_suspend: 'Suspendujete {0}. Ovaj komentar će ići na spisak odbijenih komentare i neće moći da postavlja, odgovara i prijavljuje komentare dok ne istekne suspenzija.' + email_message_suspend: 'Poštovani {0},\n\nu skladu sa {1} pravilima komentarisanja, vaš nalog je trenutno suspendovan. Tokom suspenzije ne možete da komentarišete i imate interakciju sa ostalim komentatorima. Molimo da se vratite raspravi {2}.' + hour: '{0} sat' + hours: '{0} sati' + notify_suspend_until: 'Korisnik {0} je privremeno suspendovan. Suspenzija će se automatski okončati' + one_hour: 'Sat vremena' + select_duration: 'odaberi trajanje suspenzije' + send: 'Pošalji' + suspend_user: 'Suspenduj korisnika' + title_notify: 'Obavijesti korisnika o privremenoj suspenziji' + title_suspend: 'Suspenduj korisnika' + write_message: 'Napiši poruku' + thank_you: 'Cijenimo vašu povratnu reakciju i vašu bezbjedost. Moderator će ukoro pogledati vašu prijavu' + user: + bio_flags: 'Prijave za ovu biografiju.' + user_bio: 'Korisnička biografija' + username_flags: 'Prijave za ovo korisničko ime' + user_detail: + all: 'Sve' + ban: 'Banuj korisnika' + banned: 'Banovan' + email: 'Molimo Vas unesite validan e mail' + id: 'ID' + karma: 'karma' + karma_docs_link: 'https://docs.coralproject.net/talk/trust/#user-karma-score' + learn_more: 'Saznaj više' + member_since: 'Član od' + reject_rate: 'Odbijanja' + reject_username: 'Odbij korisničko ime' + rejected: 'Odbijen' + remove_ban: 'Ukloni ban' + remove_suspension: 'Ukloni suspenziju' + suspend: 'Suspenduj korisnika' + suspended: 'Suspendovan' + total_comments: 'Svi komentari' + unreliable: 'Nepouzdano' + user_history: 'Istorija korisnika' + user_karma_score: 'Karma poeni' + username: 'Korisničko ime mora sadržati isključivo slova, brojeve i _ ' + username_needs_approval: 'Potrebno odobrenje korisničkog imena' + username_rejected: 'Korisničko ime odbijeno' + user_history: + action: 'Akcija' + ban_removed: 'Zabrana uklonjena' + date: 'Datum' + suspended: 'Suspendovan, {0}' + suspension_removed: 'Suspenzija uklonjena' + system: 'Sistem' + taken_by: 'Preduzeo' + user_banned: 'Korisnik banovan' + username_status: 'Korisnik {0}' + user_impersonating: 'Korisnik se lažno predstavlja' + user_no_comment: 'Nikada niste napisali komentar. Priključite se raspravi.' + username_offensive: 'Korisničko ime je neprimjereno.' + validators: + confirm_email: 'Email adrese se ne podudaraju, probajte ponovo' + confirm_password: 'Potvrdi šifru' + required: 'polje je obavezno' + verify_email: 'Unesite validan e mail' + verify_organization_name: 'Ime organizacije mora sadržati samo slova ili brojeve' + verify_password: 'Šifra mora imati makar 8 znakova' + verify_username: 'Korisničko ime može sadržati isključivo slova, brojeve i _' + view_conversation: 'Vidi razgovor' + your_account_has_been_banned: 'Vaš nalog je banovan' + your_account_has_been_suspended: 'Vaš nalog je privremeno suspendovan' + your_username_has_been_rejected: 'Vaš nalog je suspendovan jer je korisničko ime označeno kao neprikladno. Da povratite nalog, unesite novo korisničko ime' diff --git a/models/schema/asset.js b/models/schema/asset.js index 82de92120..6df93634b 100644 --- a/models/schema/asset.js +++ b/models/schema/asset.js @@ -81,6 +81,10 @@ Asset.index( } ); +// Indexes for listing the assets on the admin page. +Asset.index({ created_at: -1, publication_date: -1 }, { background: true }); +Asset.index({ created_at: 1, publication_date: 1 }, { background: true }); + /** * Returns true if the asset is closed, false else. */ diff --git a/models/schema/user.js b/models/schema/user.js index 40dcd5ec8..d4dca3a1c 100644 --- a/models/schema/user.js +++ b/models/schema/user.js @@ -180,6 +180,30 @@ const User = new Schema( }, ], }, + + // alwaysPremod stores the current user premod status as well as the history of + // changes. + alwaysPremod: { + // Status stores the current user premod status. + status: { + type: Boolean, + required: true, + default: false, + index: true, + }, + history: [ + { + // Status stores the historical premod status. + status: Boolean, + + // assigned_by stores the user id of the user who assigned this status. + assigned_by: { type: String, default: null }, + + // created_at stores the date when this status was assigned. + created_at: { type: Date, default: Date.now }, + }, + ], + }, }, // IgnoresUsers is an array of user id's that the current user is ignoring. @@ -359,6 +383,27 @@ User.virtual('banned') }); }); +/** + * alwaysPremod returns true when the user is currently in always premod, and sets the alwaysPremod + * status locally. + */ +User.virtual('alwaysPremod') + .get(function() { + return this.status.alwaysPremod.status; + }) + .set(function(status) { + this.status.alwaysPremod.status = status; + + if (!this.status.alwaysPremod.history) { + this.status.alwaysPremod.history = []; + } + + this.status.alwaysPremod.history.push({ + status, + created_at: new Date(), + }); + }); + /** * suspended returns true when the user is currently suspended, and sets the * suspension status locally. diff --git a/package.json b/package.json index fce56b057..ee7b68e44 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "talk", - "version": "4.7.0", + "version": "4.8.0", "description": "A better commenting experience from Mozilla, The New York Times, and the Washington Post. https://coralproject.net", "main": "app.js", "private": true, diff --git a/perms/constants/mutation.js b/perms/constants/mutation.js index 2d4ae04cb..54b92e00b 100644 --- a/perms/constants/mutation.js +++ b/perms/constants/mutation.js @@ -7,6 +7,7 @@ module.exports = { EDIT_COMMENT: 'EDIT_COMMENT', SET_USER_USERNAME_STATUS: 'SET_USER_USERNAME_STATUS', SET_USER_BAN_STATUS: 'SET_USER_BAN_STATUS', + SET_USER_ALWAYS_PREMOD_STATUS: 'SET_USER_ALWAYS_PREMOD_STATUS', SET_USER_SUSPENSION_STATUS: 'SET_USER_SUSPENSION_STATUS', SET_COMMENT_STATUS: 'SET_COMMENT_STATUS', ADD_COMMENT_TAG: 'ADD_COMMENT_TAG', diff --git a/perms/reducers/mutation.js b/perms/reducers/mutation.js index 8133fcd47..93cb0ab24 100644 --- a/perms/reducers/mutation.js +++ b/perms/reducers/mutation.js @@ -47,6 +47,7 @@ module.exports = (user, perm) => { case types.SET_USER_USERNAME_STATUS: case types.SET_USER_BAN_STATUS: case types.SET_USER_SUSPENSION_STATUS: + case types.SET_USER_ALWAYS_PREMOD_STATUS: case types.UPDATE_ASSET_SETTINGS: case types.UPDATE_ASSET_STATUS: return check(user, ['ADMIN', 'MODERATOR']); diff --git a/plugins/talk-plugin-akismet/client/translations.yml b/plugins/talk-plugin-akismet/client/translations.yml index 3dd13731b..ade9a3095 100644 --- a/plugins/talk-plugin-akismet/client/translations.yml +++ b/plugins/talk-plugin-akismet/client/translations.yml @@ -42,6 +42,21 @@ en: reasons: comment: spam_comment: "Detected Spam" +sr: + error: + COMMENT_IS_SPAM: | + Sadržaj ovog komentara liči na spam. Možete + urediti komentar, ili ga svejedno poslati moderatoru na odobrenje. + talk-plugin-akismet: + spam: "Spam" + spam_comment: "Spam" + detected: "Detected by Akismet" + still_spam: | + Hvala Vam. Naš tim moderatora će uskoro prgeldati vš komentar. + flags: + reasons: + comment: + spam_comment: "Detected Spam" es: error: COMMENT_IS_SPAM: | @@ -72,6 +87,21 @@ fr: reasons: comment: spam_comment: "Detected Spam" +he: + error: + COMMENT_IS_SPAM: | + תגובה זו נראית כמו ספאם. + תוכל לערוך את התגובה או לשלוח אותה בכל מקרה לביקורת של מפקח. + talk-plugin-akismet: + spam: "ספאם" + spam_comment: "ספאם" + detected: "זוהה על ידי אקיזמט" + still_spam: | + תודה. צוות הפיקוח שלנו יבחן את התגובה שלך בקרוב. + flags: + reasons: + comment: + spam_comment: "זוהה ספאם" nl_NL: error: COMMENT_IS_SPAM: | diff --git a/plugins/talk-plugin-auth/client/translations.yml b/plugins/talk-plugin-auth/client/translations.yml index fe6324e21..8be54055a 100644 --- a/plugins/talk-plugin-auth/client/translations.yml +++ b/plugins/talk-plugin-auth/client/translations.yml @@ -99,6 +99,51 @@ da: username: Username write_your_username: "Edit your username" your_username: "Your username appears on every comment you post." +de: + talk-plugin-auth: + login: + email_verify_cta: "Bitte bestätigen Sie Ihre E-Mail-Adresse." + request_new_verify_email: "E-Mail-Bestätigung erneut anfordern" + verify_email: "Danke, dass Sie ein Konto eingerichtet haben! Wir haben eine Nachricht an Ihre E-Mail-Adresse geschickt, mit der Sie Ihr Konto bestätigen können." + verify_email2: "Sie müssen ihr Konto bestätigen, bevor Sie beginnen können." + not_you: "Nicht Ihr Name?" + logged_in_as: "Angemeldet als" + facebook_sign_in: "Mit Facebook anmelden" + facebook_sign_up: "Mit Facebook registrieren" + logout: "Abmelden" + sign_in: "Anmelden" + sign_in_to_join: "Anmelden, um zu diskutieren" + or: "Oder" + email: "E-Mail-Adresse" + password: "Passwort" + forgot_your_pass: "Passwort vergessen?" + need_an_account: "Brauchen Sie ein Konto?" + register: "Registrieren" + sign_up: "Registrieren" + confirm_password: "Passwort bestätigen" + username: "Nutzername" + already_have_an_account: "Konto bereits vorhanden?" + recover_password: "Passwort zurücksetzen" + email_in_use: "E-Mail-Adresse bereits vergeben" + email_or_username_in_use: "E-Mail-Adresse oder Nutzername bereits vergeben" + required_field: "Pflichtfeld" + passwords_dont_match: "Passwörter nicht identisch." + special_characters: "Nutzernamen dürfen nur Buchstaben, Zahlen und _ enthalten" + sign_in_to_comment: "Zum Kommentieren anmelden" + check_the_form: "Ungültige Eingabe. Bitte prüfen Sie Ihre Felder" + set_username_dialog: + check_the_form: "Ungültige Eingabe. Bitte prüfen Sie Ihre Felder" + continue: "Mit diesem Facebook-Profilnamen fortfahren" + error_create: "Fehler bei Änderung des Nutzernamens" + fake_comment_body: "Dies ist ein Beispiel-Kommentar. Leser können im Kommentarbereich Meinungen und Gedanken mit der Redaktion austauschen." + fake_comment_date: "Vor 1 Minute" + if_you_dont_change_your_name: "Wenn Sie Ihren Nutzernamen an diesem Punkt nicht ändern, wird Ihr Facebook-Name mit Ihren Kommentaren angezeigt." + required_field: "Pflichtfeld" + save: Speichern + special_characters: "Nutzernamen dürfen nur Buchstaben, Zahlen und _ enthalten" + username: Nutzername + write_your_username: "Nutzername bearbeiten" + your_username: "Ihr Nutzername erscheint an jedem Ihrer veröffentlichten Kommentare." en: talk-plugin-auth: login: @@ -152,51 +197,6 @@ en: cancel: "Cancel" edit: "Edit" changed_password_msg: "Your password has been successfully changed" -de: - talk-plugin-auth: - login: - email_verify_cta: "Bitte bestätigen Sie Ihre E-Mail-Adresse." - request_new_verify_email: "E-Mail-Bestätigung erneut anfordern" - verify_email: "Danke, dass Sie ein Konto eingerichtet haben! Wir haben eine Nachricht an Ihre E-Mail-Adresse geschickt, mit der Sie Ihr Konto bestätigen können." - verify_email2: "Sie müssen ihr Konto bestätigen, bevor Sie beginnen können." - not_you: "Nicht Ihr Name?" - logged_in_as: "Angemeldet als" - facebook_sign_in: "Mit Facebook anmelden" - facebook_sign_up: "Mit Facebook registrieren" - logout: "Abmelden" - sign_in: "Anmelden" - sign_in_to_join: "Anmelden, um zu diskutieren" - or: "Oder" - email: "E-Mail-Adresse" - password: "Passwort" - forgot_your_pass: "Passwort vergessen?" - need_an_account: "Brauchen Sie ein Konto?" - register: "Registrieren" - sign_up: "Registrieren" - confirm_password: "Passwort bestätigen" - username: "Nutzername" - already_have_an_account: "Konto bereits vorhanden?" - recover_password: "Passwort zurücksetzen" - email_in_use: "E-Mail-Adresse bereits vergeben" - email_or_username_in_use: "E-Mail-Adresse oder Nutzername bereits vergeben" - required_field: "Pflichtfeld" - passwords_dont_match: "Passwörter nicht identisch." - special_characters: "Nutzernamen dürfen nur Buchstaben, Zahlen und _ enthalten" - sign_in_to_comment: "Zum Kommentieren anmelden" - check_the_form: "Ungültige Eingabe. Bitte prüfen Sie Ihre Felder" - set_username_dialog: - check_the_form: "Ungültige Eingabe. Bitte prüfen Sie Ihre Felder" - continue: "Mit diesem Facebook-Profilnamen fortfahren" - error_create: "Fehler bei Änderung des Nutzernamens" - fake_comment_body: "Dies ist ein Beispiel-Kommentar. Leser können im Kommentarbereich Meinungen und Gedanken mit der Redaktion austauschen." - fake_comment_date: "Vor 1 Minute" - if_you_dont_change_your_name: "Wenn Sie Ihren Nutzernamen an diesem Punkt nicht ändern, wird Ihr Facebook-Name mit Ihren Kommentaren angezeigt." - required_field: "Pflichtfeld" - save: Speichern - special_characters: "Nutzernamen dürfen nur Buchstaben, Zahlen und _ enthalten" - username: Nutzername - write_your_username: "Nutzername bearbeiten" - your_username: "Ihr Nutzername erscheint an jedem Ihrer veröffentlichten Kommentare." es: talk-plugin-auth: login: @@ -296,6 +296,49 @@ fr: username: "Nom d'utilisateur" write_your_username: "Modifier votre nom d'utilisateur" your_username: "Votre nom d'utilisateur apparaît sur chaque commentaire que vous publiez." +he: + talk-plugin-auth: + login: + email_verify_cta: "אנא אמת את כתובת האימייל שלך." + request_new_verify_email: 'בקש אימייל נוסף' + verify_email: 'תודה שיצרת חשבון! שלחנו אימייל לכתובת שסיפקת כדי לאמת את חשבונך.' + verify_email2: "עליך לאמת את חשבונך לפני שתעסוק בקהילה." + not_you: "לא אתה?" + logged_in_as: "נכנס בתור" + logout: "התנתק" + sign_in: "היכנס" + sign_in_to_join: "היכנס כדי להצטרף לשיחה" + or: "או" + email: "כתובת אימייל" + password: "סיסמה" + forgot_your_pass: "שכחת את הסיסמה?" + need_an_account: "צריך חשבון?" + register: "הירשם" + sign_up: "הירשם" + confirm_password: "אשר את הסיסמה שלך" + username: "שם משתמש" + already_have_an_account: "כבר יש לך חשבון?" + recover_password: "שחזור סיסמה" + email_in_use: "אימייל כבר תפוס" + email_or_username_in_use: "אימייל או שם משתמש כבר תפוס" + required_field: "חובה למלא את הערך הזה" + passwords_dont_match: "הסיסמאות אינן תואמות." + special_characters: "שמות משתמש יכולים להכיל אותיות, מספרים ו- _ בלבד" + sign_in_to_comment: "היכנס כדי להגיב" + check_the_form: "טופס לא חוקי. אנא, בדוק את השדות" + set_username_dialog: + check_the_form: "טופס לא חוקי. אנא, בדוק את השדות" + continue: "המשך עם אותו שם משתמש בפייסבוק" + error_create: "שגיאה בעת שינוי שם המשתמש" + fake_comment_body: "זוהי תגובה לדוגמה. הקוראים יכולים לשתף את המחשבות שלהם ואת דעות עם חדר החדשות בסעיף התגובות" + fake_comment_date: "לפני דקה אחת" + if_you_dont_change_your_name: "אם לא תשנה את שם המשתמש שלך בשלב זה שם התצוגה שלך בפייסבוק יופיע לצד כל תגובות שלך." + required_field: "חובה למלא את הערך הזה" + save: "שמור" + special_characters: "שמות משתמש יכולים להכיל אותיות, מספרים ו- _ בלבד" + username: "שם משתמש" + write_your_username: "ערוך את שם המשתמש שלך" + your_username: "שם המשתמש שלך מופיע בכל תגובה שתפרסם." nl_NL: talk-plugin-auth: login: @@ -394,6 +437,59 @@ pt_BR: username: Nome de usuário write_your_username: "Edite seu nome de usuário" your_username: "Seu nome de usuário aparece em cada comentário feito." +sr: + talk-plugin-auth: + login: + email_verify_cta: "Molimo potvrdite vašu e-mail adresu." + request_new_verify_email: "Zatraži novu email poruku" + verify_email: "Hvala Vam što ste napravili nalog! Poslali smo e-mail poruku za potvrdu naloga na adresu koju ste uneli." + verify_email2: "Morate potvrditi svoj nalog pre nego što se uključite u raspravu." + not_you: "Niste Vi?" + logged_in_as: "Prijavljeni ste kao" + logout: "Odjava" + sign_in: "Prijava" + sign_in_to_join: "Prijavite se da biste se uključili u raspravu" + or: "Ili" + email: "E-mail adresa" + password: "Lozinka" + password_error: "Lozinka mora sadržati najmanje 8 karaktera." + forgot_your_pass: "Zaboravili ste lozinku?" + need_an_account: "Nemate nalog?" + register: "Registrujte se" + sign_up: "Prijavite se" + confirm_password: "Potvrdite lozinku" + username: "Korisničko ime" + already_have_an_account: "Već imate nalog?" + recover_password: "Oporavite lozinku" + email_in_use: "E-mail adresa je već u upotrebi" + email_or_username_in_use: "E-mail adresa ili korisničko ime su već u upotrebi" + required_field: "Ovo polje je obavezno" + passwords_dont_match: "Lozinke se ne poklapaju." + special_characters: "Korisničko ime može sadržati isključivo slova, brojeve ili _" + sign_in_to_comment: "Prijava za komentarisanje" + check_the_form: "Neispravan formular. Molimo, proverite polja" + set_username_dialog: + check_the_form: "Neispravan formular. Molimo, proverite polja" + continue: "Nastavite sa istim Facebook imenom" + error_create: "Greška pri promeni korisničkog imena" + fake_comment_body: "Ovo je jedan ogledni komentar. Čitaoci mogu da dele svoje stavove i mišljenja sa uredništvom u sekciji sa komentarima." + fake_comment_date: "Pre jedan minut" + if_you_dont_change_your_name: "Ukoliko ne promenite korisničko ime u ovom koraku vaše Facebook ime će se pojavljivati uz vaše komentare." + required_field: "Obavezno polje" + save: Snimi + special_characters: "Korisničko ime može sadržati isključivo slova, brojeve ili _" + username: Korisničko ime + write_your_username: "Uredi svoje korisničko ime" + your_username: "Vaše korisničko ime prikazuje se uz svaki komentar koji pošaljete." + change_password: + change_password: "Promeni lozinku" + passwords_dont_match: "Lozinke se ne poklapaju" + required_field: "Polje je obavezno" + forgot_password: "Zaboravili ste lozinku?" + save: "Snimi" + cancel: "Poništi" + edit: "Uredi" + changed_password_msg: "Vaša lozinka je uspešno promenjena" zh_CN: talk-plugin-auth: login: diff --git a/plugins/talk-plugin-author-menu/client/translations.yml b/plugins/talk-plugin-author-menu/client/translations.yml index 4335b17e1..3b4c32284 100644 --- a/plugins/talk-plugin-author-menu/client/translations.yml +++ b/plugins/talk-plugin-author-menu/client/translations.yml @@ -2,19 +2,23 @@ ar: talk-plugin-author-menu: da: talk-plugin-author-menu: -en: - talk-plugin-author-menu: de: talk-plugin-author-menu: +en: + talk-plugin-author-menu: es: talk-plugin-author-menu: fr: talk-plugin-author-menu: +he: + talk-plugin-author-menu: nl_NL: talk-plugin-author-menu: pt_BR: talk-plugin-author-menu: +sr: + talk-plugin-author-menu: zh_CN: talk-plugin-author-menu: zh_TW: - talk-plugin-author-menu: + talk-plugin-author-menu: \ No newline at end of file diff --git a/plugins/talk-plugin-facebook-auth/README.md b/plugins/talk-plugin-facebook-auth/README.md index 08780adda..839265b04 100644 --- a/plugins/talk-plugin-facebook-auth/README.md +++ b/plugins/talk-plugin-facebook-auth/README.md @@ -29,6 +29,8 @@ Configuration: guide. This is only required while the `talk-plugin-facebook-auth` plugin is enabled. +_NOTE: FabceBook auth requires your site to use `https` (SSL) not `http`. If your site is not `https` you can not use this plugin!_ + ## GDPR Compliance In order to facilitate compliance with the diff --git a/plugins/talk-plugin-facebook-auth/client/translations.yml b/plugins/talk-plugin-facebook-auth/client/translations.yml index ca98e390b..985c930a7 100644 --- a/plugins/talk-plugin-facebook-auth/client/translations.yml +++ b/plugins/talk-plugin-facebook-auth/client/translations.yml @@ -18,6 +18,10 @@ fr: talk-plugin-facebook-auth: sign_in: "Connectez-vous avec Facebook" sign_up: "Inscrivez-vous avec Facebook" +he: + talk-plugin-facebook-auth: + sign_in: "התחבר עם פייסבוק" + sign_up: "הרשם עם פייסבוק" nl_NL: talk-plugin-facebook-auth: sign_in: "Inloggen met Facebook" diff --git a/plugins/talk-plugin-featured-comments/client/translations.yml b/plugins/talk-plugin-featured-comments/client/translations.yml index 9db5e772e..98729af44 100644 --- a/plugins/talk-plugin-featured-comments/client/translations.yml +++ b/plugins/talk-plugin-featured-comments/client/translations.yml @@ -28,21 +28,6 @@ da: are_you_sure: Are you sure you would like to feature this comment? cancel: Cancel yes_feature_comment: Yes, feature comment -en: - talk-plugin-featured-comments: - un_feature: Un-Feature - feature: Feature - featured: Featured - featured_comments: Featured Comments - go_to_conversation: Go to conversation - tooltip_description: Comments selected by our team as worth reading - notify_self_featured: 'The comment from {0} is now featured and approved' - notify_featured: '{0} featured and approved comment "{1}"' - notify_unfeatured: '{0} unfeatured comment "{1}"' - feature_comment: Feature comment? - are_you_sure: Are you sure you would like to feature this comment? - cancel: Cancel - yes_feature_comment: Yes, feature comment de: talk-plugin-featured-comments: un_feature: Empfehlung aufheben @@ -58,6 +43,21 @@ de: are_you_sure: Sind Sie sicher, dass Sie diesen Kommentar empfehlen möchten? cancel: Abbrechen yes_feature_comment: Ja, empfehlen +en: + talk-plugin-featured-comments: + un_feature: Un-Feature + feature: Feature + featured: Featured + featured_comments: Featured Comments + go_to_conversation: Go to conversation + tooltip_description: Comments selected by our team as worth reading + notify_self_featured: 'The comment from {0} is now featured and approved' + notify_featured: '{0} featured and approved comment "{1}"' + notify_unfeatured: '{0} unfeatured comment "{1}"' + feature_comment: Feature comment? + are_you_sure: Are you sure you would like to feature this comment? + cancel: Cancel + yes_feature_comment: Yes, feature comment es: talk-plugin-featured-comments: un_feature: Desmarcar @@ -85,6 +85,21 @@ fr: are_you_sure: Are you sure you would like to feature this comment? cancel: Cancel yes_feature_comment: Yes, feature comment +he: + talk-plugin-featured-comments: + un_feature: תסיר המלצה + feature: ממליץ + featured: מומלצים + featured_comments: תגובות מומלצות + go_to_conversation: עבור אל השיחה + tooltip_description: תגובות שנבחרו על ידי הצוות שלנו כקריאה שווה + notify_self_featured: 'ההערה מ {0} מומלצת ומאושרת כעת' + notify_featured: '"{0} המליץ ואושר תגובה {1}"' + notify_unfeatured: '{0} הוסר את ההמלצה מתגובה "{1}"' + feature_comment: ממליץ תגובה? + are_you_sure: האם אתה בטוח שברצונך להמליץ את התגובה הזו? + cancel: בטל + yes_feature_comment: כן, ממליץ תגובה nl_NL: talk-plugin-featured-comments: un_feature: Uitlichten ongedaan maken. @@ -115,6 +130,21 @@ pt_BR: are_you_sure: Você tem certeza que deseja destacar esse comentário? cancel: Cancelar yes_feature_comment: Sim, destacar comentário +sr: + talk-plugin-featured-comments: + un_feature: Skini sa izdvojenih + feature: Označi kao izdvojen + featured: Izdvojen + featured_comments: Izdvojeni komentari + go_to_conversation: Idi na raspravu + tooltip_description: Komentari izdvojeni od strane redakcije kao posebno vredni pažnje + notify_self_featured: 'Komentar od {0} je sada izdvojen i odobren' + notify_featured: '{0} je izdvojio i odobrio komentar "{1}"' + notify_unfeatured: '{0} je skinuo sa izdvojenih komentara "{1}"' + feature_comment: Izdvoj komentar? + are_you_sure: Sigurni ste da želite da izdvojite ovaj komentar? + cancel: Poništi + yes_feature_comment: Da, izdvoj komentar zh_CN: talk-plugin-featured-comments: un_feature: "取消精选" diff --git a/plugins/talk-plugin-flag-details/client/translations.yml b/plugins/talk-plugin-flag-details/client/translations.yml index 163cb8a5d..a112b1799 100644 --- a/plugins/talk-plugin-flag-details/client/translations.yml +++ b/plugins/talk-plugin-flag-details/client/translations.yml @@ -3,28 +3,34 @@ ar: flags: تقارير da: talk-plugin-flag-details: - flags: Reports -en: - talk-plugin-flag-details: - flags: Reports + flags: Reports de: talk-plugin-flag-details: flags: Markierungen +en: + talk-plugin-flag-details: + flags: Reports es: talk-plugin-flag-details: flags: Reportes fr: talk-plugin-flag-details: flags: Reports +he: + talk-plugin-flag-details: + flags: דיווחים nl_NL: talk-plugin-flag-details: flags: Meldingen pt_BR: talk-plugin-flag-details: flags: Reports +sr: + talk-plugin-flag-details: + flags: Reporteri zh_CN: talk-plugin-flag-details: flags: "报告" zh_TW: talk-plugin-flag-details: - flags: 標記 + flags: 標記 \ No newline at end of file diff --git a/plugins/talk-plugin-google-auth/client/translations.yml b/plugins/talk-plugin-google-auth/client/translations.yml index 70e184d4d..19937bea8 100644 --- a/plugins/talk-plugin-google-auth/client/translations.yml +++ b/plugins/talk-plugin-google-auth/client/translations.yml @@ -14,6 +14,10 @@ fr: talk-plugin-google-auth: sign_in: "Connectez-vous avec Google" sign_up: "Inscrivez-vous avec Google" +he: + talk-plugin-google-auth: + sign_in: "התחבר עם גוגל" + sign_up: "הרשם עם גוגל" nl_NL: talk-plugin-google-auth: sign_in: "Inloggen met Google" diff --git a/plugins/talk-plugin-ignore-user/client/translations.yml b/plugins/talk-plugin-ignore-user/client/translations.yml index 1c55b25d9..4562bf266 100644 --- a/plugins/talk-plugin-ignore-user/client/translations.yml +++ b/plugins/talk-plugin-ignore-user/client/translations.yml @@ -24,6 +24,17 @@ da: notify_success: | You are now ignoring {0}. You can undo this action from My Profile. confirmation_title: Ignore {0}? +de: + talk-plugin-ignore-user: + blank_info: Sie ignorieren derzeit keine Nutzer + section_title: Ignorierte Nutzer + section_info: Weil Sie die folgenden Nutzer ignorieren, sind deren Kommentare versteckt. + stop_ignoring: Ignorieren beenden + ignore_user: Nutzer ignorieren + cancel: Abbrechen + confirmation: Wenn Sie Nutzer ignorieren, werden deren Kommentare auf der Seite für Sie unsichtbar. Diese Einstellung können Sie unter Mein Profil ändern. + notify_success: Sie ignorieren jetzt {0}. Sie können diese Einstellung unter Mein Profil wieder aufheben. + confirmation_title: "{0} ignorieren?" en: talk-plugin-ignore-user: blank_info: You are currently not ignoring any users @@ -38,17 +49,6 @@ en: notify_success: | You are now ignoring {0}. You can undo this action from My Profile. confirmation_title: Ignore {0}? -de: - talk-plugin-ignore-user: - blank_info: Sie ignorieren derzeit keine Nutzer - section_title: Ignorierte Nutzer - section_info: Weil Sie die folgenden Nutzer ignorieren, sind deren Kommentare versteckt. - stop_ignoring: Ignorieren beenden - ignore_user: Nutzer ignorieren - cancel: Abbrechen - confirmation: Wenn Sie Nutzer ignorieren, werden deren Kommentare auf der Seite für Sie unsichtbar. Diese Einstellung können Sie unter Mein Profil ändern. - notify_success: Sie ignorieren jetzt {0}. Sie können diese Einstellung unter Mein Profil wieder aufheben. - confirmation_title: "{0} ignorieren?" es: talk-plugin-ignore-user: section_title: "Usuarios ignorados" @@ -76,6 +76,19 @@ fr: notify_success: | You are now ignoring {0}. You can undo this action from My Profile. confirmation_title: Ignore {0}? +he: + talk-plugin-ignore-user: + blank_info: בשלב זה אינך מתעלם ממשתמשים + section_title: משתמשים שהתעלמו מהם + section_info: מכיוון שהתעלמת מהמגיבים הבאים, ההערות שלהם מוסתרות. + stop_ignoring: הפסק להתעלם + ignore_user: התעלם מהמשתמש + cancel: בטל + confirmation: | + כאשר תתעלם ממשתמש, כל התגובות שכתבו באתר יוסתרו ממך. תוכל לבטל זאת מאוחר יותר מהפרופיל שלי. + notify_success: | + כעת אתה מתעלם {0}. תוכל לבטל את הפעולה הזו מהפרופיל שלי. + confirmation_title: "התעלם {0}?" nl_NL: talk-plugin-ignore-user: blank_info: Momenteel negeer je geen gebruikers @@ -104,6 +117,20 @@ pt_BR: notify_success: | Agora você está ignorando {0}. Você pode desfazer essa ação em Meu Perfil. confirmation_title: Ignorar {0}? +sr: + talk-plugin-ignore-user: + blank_info: Trenutno ne ignorišete ni jednog korisnika + section_title: Ignorisani korisnici + section_info: Pošto ste ignorisali sledeće komentatore, njihovi komentari su sakriveni. + stop_ignoring: Prestani sa ignorisanjem + ignore_user: Ignoriši korisnika + cancel: Poništi + confirmation: | + Kada ignorišete nekog korisnika, svi njegovi komentari na sajtu biće skriveni od vas. Ovo podešavanje možete + uvek isključiti u prikazu Moj profil. + notify_success: | + Sada ignorišete {0}. Možete poništiti ovu radnju iz prikaza Moj profil. + confirmation_title: Ignoriši {0}? zh_CN: talk-plugin-ignore-user: section_title: "被忽略用户" diff --git a/plugins/talk-plugin-like/client/translations.yml b/plugins/talk-plugin-like/client/translations.yml index f122aa024..de1df8db0 100644 --- a/plugins/talk-plugin-like/client/translations.yml +++ b/plugins/talk-plugin-like/client/translations.yml @@ -6,14 +6,14 @@ da: talk-plugin-like: like: Like liked: Liked -en: - talk-plugin-like: - like: Like - liked: Liked de: talk-plugin-like: like: Gefällt mir liked: Gefällt mir +en: + talk-plugin-like: + like: Like + liked: Liked es: talk-plugin-like: like: Me Gusta @@ -22,6 +22,10 @@ fr: talk-plugin-like: like: Like liked: Liked +he: + talk-plugin-like: + like: לייק + liked: לייקד nl_NL: talk-plugin-like: like: Like @@ -30,6 +34,10 @@ pt_BR: talk-plugin-like: like: Like liked: Liked +sr: + talk-plugin-like: + like: Sviđa mi se + liked: Sviđa vam se zh_CN: talk-plugin-like: like: "喜欢" diff --git a/plugins/talk-plugin-local-auth/translations.yml b/plugins/talk-plugin-local-auth/translations.yml index a13b005b5..18b2a0b44 100644 --- a/plugins/talk-plugin-local-auth/translations.yml +++ b/plugins/talk-plugin-local-auth/translations.yml @@ -150,6 +150,82 @@ en: description_2: "You can change your account settings by visiting" path: "My Profile > Settings" alert: "Email Added!" +sr: + email: + email_change_original: + subject: Promena e-maila + body: Vaša e-mail adresa je promenjena iz {0} u {1}. Ukoliko niste zatražili ovu izmenu, molimo kontaktirajte {2}. + error: + NO_LOCAL_PROFILE: Nijedna postojeća e-mail adresa nije povezana sa ovim nalogom. + LOCAL_PROFILE: E-mail adresa je već povezana sa ovim nalogom. + INCORRECT_PASSWORD: Lozinka koju ste uneli je neispravna. + talk-plugin-local-auth: + change_password: + change_password: "Promenite lozinku" + passwords_dont_match: "Lozinke se ne poklapaju" + required_field: "Ovo polje je obavezno" + forgot_password: "Zaboravili ste lozinku?" + old_password: "Stara lozinka" + new_password: "Nova lozinka" + confirm_new_password: "Potvrdite novu lozinku" + save: "Snimi" + cancel: "Poništi" + edit: "Uredi" + changed_password_msg: "Promena lozinke - Vša lozinka je uspešno promenjena" + forgot_password_sent: "Zaboravljena lozinka - poslali smo Vam e-mail za povraćaj lozinke" + change_username: + change_username_note: "Korisničko ime može biti promenjeno jednom u 14 dana." + is_not_eligible: "Trenutno ne možete promeniti korisničko ime." + save: "Snimi" + edit_profile: "Uredi profil" + cancel: "Poništi" + confirm_username_change: "Potvrdi promenu korisničkog imena" + description: "Pokušavate da promenite svoje korisničko ime. Novo korisničko ime će se pojaviti na svim vašim prethodnim i novim komentarima." + old_username: "Staro korisničko ime" + new_username: "Novo korisničko ime" + re_enter: "Ponovo unesite korisničko ime" + bottom_note: "Napomena: Nećete moći ponovo da menjate svoje korisničko ime narednih 14 dana" + confirm_changes: "Potvrdi izmene" + username_does_not_match: "Korisničko ime ne postoji" + cant_be_equal: "Vaše novo {0} mora se razlikovati od prethodnog" + changed_username_success_msg: "Korisničko ime je promenjeno - Vaše korisničko ime je uspešno promenjeno. Nećete moći ponovo da ga menjate narednih 14 dana." + change_username_attempt: "Koriničko ime ne može biti promenjeno. Korisničko ime može biti promenjeno samo jednom u 14 dana." + change_email: + confirm_email_change: "Potvrdi promenu e-maila" + description: "Pokušavate da promenite e-mail adresu. Vaša nova e-mail adresa biće korišćena za prijavljivanje i primanje obaveštenja o nalogu." + old_email: "Stara e-mail adresa" + new_email: "Nova e-mail adresa" + enter_password: "Unesite lozinku" + incorrect_password: "Pogrešna lozinka" + confirm_change: "Potvrdi izmenu" + cancel: "Poništi" + change_email_msg: "E-mail adresa promenjena. Ova e-mail adresa biće korišćena za prijavljivanje i primanje obaveštenja." + add_email: + add_email_address: "Dodaj e-mail adresu" + enter_email_address: "Unesi e-mail adresu:" + invalid_email_address: "Pogrešna e-mail adresa" + confirm_email_address: "Potvrdi e-mail adresu:" + email_does_not_match: "Email adrese se ne poklapaju" + insert_password: "Unesi lozinku:" + confirm_password: "Potvrdi lozinku:" + required_field: "Ovo polje je obavezno" + done: "Gotovo" + content: + title: "Dodaj e-mail adresu" + description: "For your added security, we require users to add an email address to their accounts. Your email address will be used to:" + item_1: "Receive updates regarding any changes to your account (email address, username, password, etc.)" + item_2: "Omogućuje vam da preuzmete (download) svoje komentare." + item_3: "Send comment notifications that you have chosen to receive." + verify: + title: "Potvrdite e-mail adresu" + description: "Poslali smo e-mail na {0} da potvrdite ssvoj nalog. Morate potvrditi e-mail adresu da bi se mogla koristiti za potvrde promene naloga i obaveštenja." + added: + title: "Email adresa dodata" + description: "Vaša e-mail adresa je dodata u nalog." + subtitle: "Želite da promenite e-mail adresu?" + description_2: "Možete urediti svoj nalog ovde:" + path: "Moj profil > Podešavanja" + alert: "E-mail dodat" pt_BR: email: email_change_original: diff --git a/plugins/talk-plugin-love/client/translations.yml b/plugins/talk-plugin-love/client/translations.yml index c42216844..c0ad10421 100644 --- a/plugins/talk-plugin-love/client/translations.yml +++ b/plugins/talk-plugin-love/client/translations.yml @@ -5,15 +5,15 @@ ar: da: talk-plugin-love: love: Love - loved: Loved -en: - talk-plugin-love: - love: Love - loved: Loved + loved: Loved de: talk-plugin-love: love: Ich liebe es loved: Ich liebe es +en: + talk-plugin-love: + love: Love + loved: Loved es: talk-plugin-love: love: Amo @@ -22,6 +22,10 @@ fr: talk-plugin-love: love: Love loved: Loved +he: + talk-plugin-love: + love: אוהב + loved: אהבתי nl_NL: talk-plugin-love: love: Love @@ -30,6 +34,10 @@ pt_BR: talk-plugin-love: love: Love loved: Loved +sr: + talk-plugin-love: + love: Volim ovo + loved: Volite ovo zh_CN: talk-plugin-love: love: " 比心" diff --git a/plugins/talk-plugin-member-since/client/translations.yml b/plugins/talk-plugin-member-since/client/translations.yml index 5f5fb4004..070e708be 100644 --- a/plugins/talk-plugin-member-since/client/translations.yml +++ b/plugins/talk-plugin-member-since/client/translations.yml @@ -3,25 +3,31 @@ ar: member_since: "عضو منذ" da: talk-plugin-member-since: - member_since: "Member Since" -en: - talk-plugin-member-since: - member_since: "Member Since" + member_since: "Member Since" de: talk-plugin-member-since: member_since: "Mitglied seit" +en: + talk-plugin-member-since: + member_since: "Member Since" es: talk-plugin-member-since: member_since: "Miembro desde" fr: talk-plugin-member-since: member_since: "Member Since" +he: + talk-plugin-member-since: + member_since: "חבר מאז" nl_NL: talk-plugin-member-since: member_since: "Lid sinds" pt_BR: talk-plugin-member-since: member_since: "Membro desde" +sr: + talk-plugin-member-since: + member_since: "Član od" zh_CN: talk-plugin-member-since: member_since: "成员加入日期" diff --git a/plugins/talk-plugin-moderation-actions/client/translations.yml b/plugins/talk-plugin-moderation-actions/client/translations.yml index 27420bedb..88e6bf8be 100644 --- a/plugins/talk-plugin-moderation-actions/client/translations.yml +++ b/plugins/talk-plugin-moderation-actions/client/translations.yml @@ -70,6 +70,18 @@ fr: ban_user_dialog_cancel: "Cancel" ban_user_dialog_yes: "Yes. Ban user" ban_user_dialog_headline: "Ban User?" +he: + talk-plugin-moderation-actions: + reject_comment: "דחה" + approve_comment: "אשר" + approved_comment: "מאושרת" + moderation_actions: "פעולות מתווך" + ban_user: "חסום משתמש" + ban_user_dialog_sub: "האם אתה בטוח שאתה רוצה לאסור את המשתמש הזה?" + ban_user_dialog_copy: "הערה: חסימת משתמש זה תציב גם תגובה זו בתור נדחה." + ban_user_dialog_cancel: "בטל" + ban_user_dialog_yes: "כן. חסום משתמש" + ban_user_dialog_headline: "חסום משתמש?" nl_NL: talk-plugin-moderation-actions: reject_comment: "Afkeuren" diff --git a/plugins/talk-plugin-notifications-category-featured/client/translations.yml b/plugins/talk-plugin-notifications-category-featured/client/translations.yml index 26e3e9a67..724832dc2 100644 --- a/plugins/talk-plugin-notifications-category-featured/client/translations.yml +++ b/plugins/talk-plugin-notifications-category-featured/client/translations.yml @@ -1,15 +1,18 @@ ar: talk-plugin-notifications-category-featured: toggle_description: تعليقي تم تمييزه +de: + talk-plugin-notifications-category-featured: + toggle_description: Mein Kommentar wird empfohlen en: talk-plugin-notifications-category-featured: toggle_description: My comment is featured es: talk-plugin-notifications-category-featured: toggle_description: Mi comentario esta presentado -de: +he: talk-plugin-notifications-category-featured: - toggle_description: Mein Kommentar wird empfohlen + toggle_description: התגובה שלי מובלטת nl_NL: talk-plugin-notifications-category-featured: - toggle_description: Mijn bericht is uitgelicht + toggle_description: Mijn bericht is uitgelicht \ No newline at end of file diff --git a/plugins/talk-plugin-notifications-category-featured/translations.yml b/plugins/talk-plugin-notifications-category-featured/translations.yml index f144c859d..b5b467474 100644 --- a/plugins/talk-plugin-notifications-category-featured/translations.yml +++ b/plugins/talk-plugin-notifications-category-featured/translations.yml @@ -5,27 +5,39 @@ ar: subject: "تميَزت واحدة من تعليقاتك على {0}" body: "{0}\n حدد أحد أعضاء فريقنا هذا التعليق ليتم عرضه كتعليق مميز للقراء الآخرين: {1}" -en: - talk-plugin-notifications: - categories: - featured: - subject: "One of your comments was featured on {0}" - body: "{0}\nA member of our team has selected this comment to be featured for other readers: {1}" -es: - talk-plugin-notifications: - categories: - featured: - subject: "Uno de tus comentarios apareció en {0}" - body: "{0}\nUn miembro de nuestro equipo ha seleccionado este comentario para ser presentado por otros lectores: {1}" de: talk-plugin-notifications: categories: featured: subject: "Einer Ihrer Kommentare wurde auf {0} hervorgehoben" body: "{0}\nEin Mitglied unseres Teams hat diesen Kommentar ausgewählt, er wird jetzt für andere Leser besonders hervorgehoben: {1}" +en: + talk-plugin-notifications: + categories: + featured: + subject: "One of your comments was featured on {0}" + body: "{0}\nA member of our team has selected this comment to be featured for other readers: {1}" +sr: + talk-plugin-notifications: + categories: + featured: + subject: "Jedan Vaš komentar je izdvojen na {0}" + body: "{0}\nČlan našeg tima je izabrao ovaj komentar da bude izdvojen za druge čitaoce: {1}" +es: + talk-plugin-notifications: + categories: + featured: + subject: "Uno de tus comentarios apareció en {0}" + body: "{0}\nUn miembro de nuestro equipo ha seleccionado este comentario para ser presentado por otros lectores: {1}" +he: + talk-plugin-notifications: + categories: + featured: + subject: "אחת מהתגובות שלך הוצגה ב {0}" + body: "{0} \n חבר בצוות שלנו בחר תגובה זו כך שתוצג עבור קוראים אחרים: {1}" nl_NL: talk-plugin-notifications: categories: featured: subject: "Een van je reacties is uitgelicht in {0}" - body: "{0}\nDe redactie heeft deze reactie uitgelicht voor andere lezers: {1}" + body: "{0}\nDe redactie heeft deze reactie uitgelicht voor andere lezers: {1}" \ No newline at end of file diff --git a/plugins/talk-plugin-notifications-category-moderation-actions/README.md b/plugins/talk-plugin-notifications-category-moderation-actions/README.md new file mode 100644 index 000000000..62485037b --- /dev/null +++ b/plugins/talk-plugin-notifications-category-moderation-actions/README.md @@ -0,0 +1,19 @@ +--- +title: talk-plugin-notifications-moderation-actions +permalink: /plugin/talk-plugin-notifications-moderation-actions/ +layout: plugin +plugin: + name: talk-plugin-notifications-moderation-actions + depends: + - name: talk-plugin-notifications + provides: + - Server + - Client +--- + +When a comment that is initially withheld from publication and is then +approved or rejected, the user will receive a notification email. + +## Configuration: + +- `TALK_MODERATION_NOTIFICATION_TYPES`. This plugin requires values to be set. Available options: `APPROVED`, `REJECTED` as a single string (comma separated). diff --git a/plugins/talk-plugin-notifications-category-moderation-actions/client/.eslintrc.json b/plugins/talk-plugin-notifications-category-moderation-actions/client/.eslintrc.json new file mode 100644 index 000000000..c8a6db18a --- /dev/null +++ b/plugins/talk-plugin-notifications-category-moderation-actions/client/.eslintrc.json @@ -0,0 +1,3 @@ +{ + "extends": "@coralproject/eslint-config-talk/client" +} diff --git a/plugins/talk-plugin-notifications-category-moderation-actions/client/index.js b/plugins/talk-plugin-notifications-category-moderation-actions/client/index.js new file mode 100644 index 000000000..b00b6cadd --- /dev/null +++ b/plugins/talk-plugin-notifications-category-moderation-actions/client/index.js @@ -0,0 +1,14 @@ +import translations from './translations.yml'; +import { t } from 'plugin-api/beta/client/services'; +import { createSettingsToggle } from 'talk-plugin-notifications/client/api/factories'; + +const SettingsToggle = createSettingsToggle('onModeration', () => + t('talk-plugin-notifications-category-moderation-actions.toggle_description') +); + +export default { + slots: { + notificationSettings: [SettingsToggle], + }, + translations, +}; diff --git a/plugins/talk-plugin-notifications-category-moderation-actions/client/translations.yml b/plugins/talk-plugin-notifications-category-moderation-actions/client/translations.yml new file mode 100644 index 000000000..e9e982949 --- /dev/null +++ b/plugins/talk-plugin-notifications-category-moderation-actions/client/translations.yml @@ -0,0 +1,6 @@ +en: + talk-plugin-notifications-category-moderation-actions: + toggle_description: My pending comments have been reviewed +es: + talk-plugin-notifications-category-moderation-actions: + toggle_description: Mis comentarios pendientes han sido revisados diff --git a/plugins/talk-plugin-notifications-category-moderation-actions/config.js b/plugins/talk-plugin-notifications-category-moderation-actions/config.js new file mode 100644 index 000000000..be850bbc8 --- /dev/null +++ b/plugins/talk-plugin-notifications-category-moderation-actions/config.js @@ -0,0 +1,8 @@ +const MODERATION_NOTIFICATION_TYPES = + (process.env.TALK_MODERATION_NOTIFICATION_TYPES && + process.env.TALK_MODERATION_NOTIFICATION_TYPES.split(',')) || + []; + +module.exports = { + MODERATION_NOTIFICATION_TYPES, +}; diff --git a/plugins/talk-plugin-notifications-category-moderation-actions/index.js b/plugins/talk-plugin-notifications-category-moderation-actions/index.js new file mode 100644 index 000000000..92406550a --- /dev/null +++ b/plugins/talk-plugin-notifications-category-moderation-actions/index.js @@ -0,0 +1,153 @@ +const { get } = require('lodash'); +const path = require('path'); + +const { MODERATION_NOTIFICATION_TYPES } = require('./config'); + +const PENDING_STATUS_TYPES = ['PREMOD', 'SYSTEM_WITHHELD']; +const AVAILABLE_NOTIFICATION_TYPES = ['APPROVED', 'REJECTED']; + +const handle = async (ctx, comment) => { + const commentID = get(comment, 'id', null); + if (commentID === null) { + ctx.log.info('could not get comment id'); + return; + } + + // Check to see if this was a pending comment. + const commentHistory = get(comment, 'status_history', []); + let wasPending = false; + + // Check for last status before current one + if (commentHistory.length >= 2) { + const previousStatus = commentHistory[commentHistory.length - 2]; + if (PENDING_STATUS_TYPES.includes(previousStatus.type)) { + wasPending = true; + } + } + + if (!wasPending) { + ctx.log.info('comment was not pending'); + return; + } + + // Execute the graph request. + const commentQl = await ctx.graphql( + ` + query GetAuthorUserMetadata($comment_id: ID!) { + comment(id: $comment_id) { + id + user { + id + notificationSettings { + onModeration + } + } + } + } + `, + { comment_id: commentID } + ); + if (commentQl.errors) { + ctx.log.error( + { err: commentQl.errors }, + 'could not query for author metadata' + ); + return; + } + + // Check if the user has notifications enabled. + const enabled = get( + commentQl, + 'data.comment.user.notificationSettings.onModeration', + false + ); + if (!enabled) { + return; + } + + const userID = get(commentQl, 'data.comment.user.id', null); + if (!userID) { + ctx.log.info('could not get comment user id'); + return; + } + + // The user does have notifications for moderated comments enabled, queue the + // notification to be sent. + return { userID, date: comment.created_at, context: comment.id }; +}; + +const hydrate = async (ctx, category, context) => { + const comment = await ctx.graphql( + ` + query GetNotificationData($context: ID!) { + comment(id: $context) { + id + asset { + title + url + } + status_history { + type + } + } + } + `, + { context } + ); + if (comment.errors) { + throw comment.errors; + } + + const commentData = get(comment, 'data.comment'); + + const headline = get(commentData, 'asset.title', null); + const assetURL = get(commentData, 'asset.url', null); + const permalink = `${assetURL}?commentId=${commentData.id}`; + return [headline, permalink]; +}; + +const handlers = { + approved: { + handle, + category: 'moderation-actions.approved', + event: 'commentAccepted', + hydrate, + digestOrder: 10, + }, + rejected: { + handle, + category: 'moderation-actions.rejected', + event: 'commentRejected', + hydrate, + digestOrder: 10, + }, +}; + +const notifications = []; +MODERATION_NOTIFICATION_TYPES.forEach(type => { + if (AVAILABLE_NOTIFICATION_TYPES.includes(type)) { + notifications.push(handlers[type.toLowerCase()]); + } else { + console.error(`Unkown moderation notification type: ${type}`); + } +}); + +module.exports = { + typeDefs: ` + type NotificationSettings { + onModeration: Boolean! + } + + input NotificationSettingsInput { + onModeration: Boolean + } + `, + resolvers: { + NotificationSettings: { + // onModeration returns false by default if not specified. + onModeration: settings => get(settings, 'onModeration', false), + }, + }, + translations: path.join(__dirname, 'translations.yml'), + notifications, +}; diff --git a/plugins/talk-plugin-notifications-category-moderation-actions/translations.yml b/plugins/talk-plugin-notifications-category-moderation-actions/translations.yml new file mode 100644 index 000000000..858c6049c --- /dev/null +++ b/plugins/talk-plugin-notifications-category-moderation-actions/translations.yml @@ -0,0 +1,20 @@ +en: + talk-plugin-notifications: + categories: + moderation-actions: + approved: + subject: "Your comment on {0} has been published" + body: "{0}\nThank you for submitting your comment. Your comment has now been published: {1}" + rejected: + subject: "Your comment on {0} was not published" + body: "{0}\nThe language used in one of your comments did not comply with our community guidelines, and so the comment has been removed." +es: + talk-plugin-notifications: + categories: + moderation-actions: + approved: + subject: "Tu comentario sobre {0} ha sido publicado" + body: "{0}\nGracias por enviar tu comentario. Tu comentario se ha publicado: {1}" + rejected: + subject: "Tu comentario en {0} no fue publicado" + body: "{0}\nEl lenguaje utilizado en uno de tus comentarios no cumple con las pautas de nuestra comunidad, por lo que el comentario se eliminó." diff --git a/plugins/talk-plugin-notifications-category-reply/client/translations.yml b/plugins/talk-plugin-notifications-category-reply/client/translations.yml index 08fbb7fc6..1e09a8eff 100644 --- a/plugins/talk-plugin-notifications-category-reply/client/translations.yml +++ b/plugins/talk-plugin-notifications-category-reply/client/translations.yml @@ -1,15 +1,18 @@ ar: talk-plugin-notifications-category-reply: toggle_description: يتلقى تعليقي ردا +de: + talk-plugin-notifications-category-reply: + toggle_description: Jemand antwortet auf meinen Kommentar en: talk-plugin-notifications-category-reply: toggle_description: My comment receives a reply es: talk-plugin-notifications-category-reply: toggle_description: Mi comentario recibe una respuesta -de: +he: talk-plugin-notifications-category-reply: - toggle_description: Jemand antwortet auf meinen Kommentar + toggle_description: התגובה שלי מקבלת תשובה nl_NL: talk-plugin-notifications-category-reply: toggle_description: Iemand antwoord op mijn reactie diff --git a/plugins/talk-plugin-notifications-category-reply/translations.yml b/plugins/talk-plugin-notifications-category-reply/translations.yml index 87b656865..e4d5facde 100644 --- a/plugins/talk-plugin-notifications-category-reply/translations.yml +++ b/plugins/talk-plugin-notifications-category-reply/translations.yml @@ -5,27 +5,39 @@ ar: subject: "رد شخص ما على تعليقك على {0}" body: "{0}\n{1} رد على تعليقك {2}" -en: - talk-plugin-notifications: - categories: - reply: - subject: "Someone has replied to your comment on {0}" - body: "{0}\n{1} replied to your comment: {2}" -es: - talk-plugin-notifications: - categories: - reply: - subject: "Alguien ha respondido a tu comentario en {0}" - body: "{0}\n{1} respondió a tu comentario: {2}" de: talk-plugin-notifications: categories: reply: subject: "Jemand hat bei {0} auf Ihren Kommentar geantwortet" body: "{0}\n{1} antwortete auf Ihren Kommentar: {2}" +en: + talk-plugin-notifications: + categories: + reply: + subject: "Someone has replied to your comment on {0}" + body: "{0}\n{1} replied to your comment: {2}" +sr: + talk-plugin-notifications: + categories: + reply: + subject: "Neko je odgovorio na Vaš komentar na {0}" + body: "{0}\n{1} je odgovorio na Vaš komentar: {2}" +es: + talk-plugin-notifications: + categories: + reply: + subject: "Alguien ha respondido a tu comentario en {0}" + body: "{0}\n{1} respondió a tu comentario: {2}" +he: + talk-plugin-notifications: + categories: + reply: + subject: "מישהו השיב לתגובה שלך על {0}" + body: "{0}\n{1} השיב לתגובתך: {2}" nl_NL: talk-plugin-notifications: categories: reply: subject: "Er heeft iemand op jouw bericht {0} gereageerd" - body: "{0}\n{1} heeft gereageerd op jouw bericht: {2}" + body: "{0}\n{1} heeft gereageerd op jouw bericht: {2}" \ No newline at end of file diff --git a/plugins/talk-plugin-notifications-category-staff/client/translations.yml b/plugins/talk-plugin-notifications-category-staff/client/translations.yml index 43c2f5c01..ae422d3c8 100644 --- a/plugins/talk-plugin-notifications-category-staff/client/translations.yml +++ b/plugins/talk-plugin-notifications-category-staff/client/translations.yml @@ -1,15 +1,18 @@ ar: talk-plugin-notifications-category-staff: toggle_description: يرد أحد الموظفين على تعليقي +de: + talk-plugin-notifications-category-staff: + toggle_description: Ein Redaktionsmitglied antwortet auf meinen Kommentar en: talk-plugin-notifications-category-staff: toggle_description: A staff member replies to my comment es: talk-plugin-notifications-category-staff: toggle_description: Un miembro del personal responde a mi comentario -de: +he: talk-plugin-notifications-category-staff: - toggle_description: Ein Redaktionsmitglied antwortet auf meinen Kommentar + toggle_description: חבר צוות עונה על תגובתי nl_NL: talk-plugin-notifications-category-staff: toggle_description: Een redactielid antwoord op mijn reactie diff --git a/plugins/talk-plugin-notifications-category-staff/translations.yml b/plugins/talk-plugin-notifications-category-staff/translations.yml index 24fe7d9ea..b712bac29 100644 --- a/plugins/talk-plugin-notifications-category-staff/translations.yml +++ b/plugins/talk-plugin-notifications-category-staff/translations.yml @@ -5,24 +5,36 @@ ar: subject: "شخص ما في {0} قد رد على تعليقك" body: "{0}\n{1} يعمل ل {2} وقد رد على تعليقك: {3}" -en: - talk-plugin-notifications: - categories: - staff: - subject: "Someone at {0} has replied to your comment" - body: "{0}\n{1} works for {2} and has replied to your comment: {3}" -es: - talk-plugin-notifications: - categories: - staff: - subject: "Alguien en {0} ha respondido a tu comentario" - body: "{0}\n{1} trabaja para {2} y ha respondido a tu comentario: {3}" de: talk-plugin-notifications: categories: staff: subject: "Jemand hat bei {0} auf Ihren Kommentar geantwortet" body: "{0}\n{1} arbeitet für {2} und hat auf Ihren Kommentar geantwortet: {3}" +en: + talk-plugin-notifications: + categories: + staff: + subject: "Someone at {0} has replied to your comment" + body: "{0}\n{1} works for {2} and has replied to your comment: {3}" +sr: + talk-plugin-notifications: + categories: + staff: + subject: "Neko je na {0} odgovorio na Vaš komentar" + body: "{0}\n{1} radi za {2} i odogovorio je na Vaš komentar: {3}" +es: + talk-plugin-notifications: + categories: + staff: + subject: "Alguien en {0} ha respondido a tu comentario" + body: "{0}\n{1} trabaja para {2} y ha respondido a tu comentario: {3}" +he: + talk-plugin-notifications: + categories: + staff: + subject: "מישהו ב {0} השיב לתגובתך" + body: "{0}\n{1} עובד עבור {2} והוא השיב לתגובתך: {3}" nl_NL: talk-plugin-notifications: categories: diff --git a/plugins/talk-plugin-notifications-digest-daily/client/translations.yml b/plugins/talk-plugin-notifications-digest-daily/client/translations.yml index f71c3364b..07eb10393 100644 --- a/plugins/talk-plugin-notifications-digest-daily/client/translations.yml +++ b/plugins/talk-plugin-notifications-digest-daily/client/translations.yml @@ -2,18 +2,26 @@ ar: talk-plugin-notifications: digest_enum: DAILY: في ملخص يومي -en: - talk-plugin-notifications: - digest_enum: - DAILY: In a daily digest -es: - talk-plugin-notifications: - digest_enum: - DAILY: En un resumen diario de: talk-plugin-notifications: digest_enum: DAILY: Einmal täglich +en: + talk-plugin-notifications: + digest_enum: + DAILY: In a daily digest +sr: + talk-plugin-notifications: + digest_enum: + DAILY: U dnevnom izveštaju +es: + talk-plugin-notifications: + digest_enum: + DAILY: En un resumen diario +he: + talk-plugin-notifications: + digest_enum: + DAILY: בתקציר יומי nl_NL: talk-plugin-notifications: digest_enum: diff --git a/plugins/talk-plugin-notifications-digest-hourly/client/translations.yml b/plugins/talk-plugin-notifications-digest-hourly/client/translations.yml index cb82b6253..d3addde00 100644 --- a/plugins/talk-plugin-notifications-digest-hourly/client/translations.yml +++ b/plugins/talk-plugin-notifications-digest-hourly/client/translations.yml @@ -2,18 +2,26 @@ ar: talk-plugin-notifications: digest_enum: HOURLY: في ملخص كل ساعة -en: - talk-plugin-notifications: - digest_enum: - HOURLY: In an hourly digest -es: - talk-plugin-notifications: - digest_enum: - HOURLY: En un resumen por hora de: talk-plugin-notifications: digest_enum: HOURLY: Stündlich +en: + talk-plugin-notifications: + digest_enum: + HOURLY: In an hourly digest +sr: + talk-plugin-notifications: + digest_enum: + HOURLY: U izveštaju na svakih sat vremena +es: + talk-plugin-notifications: + digest_enum: + HOURLY: En un resumen por hora +he: + talk-plugin-notifications: + digest_enum: + HOURLY: בתקציר שעתי nl_NL: talk-plugin-notifications: digest_enum: diff --git a/plugins/talk-plugin-notifications/client/translations.yml b/plugins/talk-plugin-notifications/client/translations.yml index f1967feca..032467929 100644 --- a/plugins/talk-plugin-notifications/client/translations.yml +++ b/plugins/talk-plugin-notifications/client/translations.yml @@ -16,42 +16,6 @@ ar: digest_option: إرسال الإشعارات digest_enum: NONE: فورا -en: - talk-plugin-notifications: - settings_title: Notifications - settings_subtitle: Receive notifications when - turn_off_all: I do not want to receive notifications - banner_info: - title: Email Verification Required - text: To receive email notifications you must have a verified email address. - verify_now: Verify your email now - banner_success: - title: Email Verification Sent - text: An email has been sent to {0} containing a verification link. - banner_error: - title: Error - text: There has been an error sending your verification email. Please try again later. - digest_option: Send notifications - digest_enum: - NONE: Immediately -es: - talk-plugin-notifications: - settings_title: Notificaciones - settings_subtitle: Reciba notificaciones cuando - turn_off_all: No quiero recibir notificaciones - banner_info: - title: Se requiere verificación de correo electrónico - text: Para recibir notificaciones por correo electrónico, debe tener una dirección de correo electrónico verificada. - verify_now: Verifica tu correo electrónico ahora. - banner_success: - title: Verificación del correo electrónico enviada - text: Se envió un correo electrónico a {0} que contiene un enlace de verificación. - banner_error: - title: Error - text: Ha habido un error al enviar su correo electrónico de verificación. Por favor, inténtelo de nuevo más tarde. - digest_option: Enviar notificaciones - digest_enum: - NONE: Inmediatamente de: talk-plugin-notifications: settings_title: Benachrichtigungen @@ -70,6 +34,78 @@ de: digest_option: Benachrichtigungen senden digest_enum: NONE: Sofort +en: + talk-plugin-notifications: + settings_title: Notifications + settings_subtitle: Receive notifications when + turn_off_all: I do not want to receive notifications + banner_info: + title: Email Verification Required + text: To receive email notifications you must have a verified email address. + verify_now: Verify your email now + banner_success: + title: Email Verification Sent + text: An email has been sent to {0} containing a verification link. + banner_error: + title: Error + text: There has been an error sending your verification email. Please try again later. + digest_option: Send notifications + digest_enum: + NONE: Immediately +sr: + talk-plugin-notifications: + settings_title: Obaveštenja + settings_subtitle: Primaj obaveštenja kada + turn_off_all: Ne želim da primam obaveštenja + banner_info: + title: Neophodna je potvrda e-mail adrese + text: Da biste primali e-mail obaveštenja morate potvrditi svoju e-mail adresu. + verify_now: Potvrdite svoj email + banner_success: + title: E-mail za potvrdu poslat + text: E-mail koji sadrži link za potvrdu je poslat na {0} . + banner_error: + title: Greška + text: Došlo je do greške pri slanju e-maila za potvrdu. Molimo pokušajte kasnije. + digest_option: Pošalji obaveštenja + digest_enum: + NONE: Odmah +es: + talk-plugin-notifications: + settings_title: Notificaciones + settings_subtitle: Reciba notificaciones cuando + turn_off_all: No quiero recibir notificaciones + banner_info: + title: Se requiere verificación de correo electrónico + text: Para recibir notificaciones por correo electrónico, debe tener una dirección de correo electrónico verificada. + verify_now: Verifica tu correo electrónico ahora. + banner_success: + title: Verificación del correo electrónico enviada + text: Se envió un correo electrónico a {0} que contiene un enlace de verificación. + banner_error: + title: Error + text: Ha habido un error al enviar su correo electrónico de verificación. Por favor, inténtelo de nuevo más tarde. + digest_option: Enviar notificaciones + digest_enum: + NONE: Inmediatamente +he: + talk-plugin-notifications: + settings_title: התראות + settings_subtitle: קבל הודעות כאשר + turn_off_all: אני לא רוצה לקבל התראות + banner_info: + title: נדרש אימות אימייל + text: כדי לקבל התראות באימייל, עליך להיות בעל כתובת אימייל מאומתת. + verify_now: אמת את האימייל שלך כעת + banner_success: + title: נשלח אימות אימייל + text: הודעת אימייל נשלחה אל {0} שמכילה קישור לאימות. + banner_error: + title: שגיאה + text: אירעה שגיאה בשליחת אימייל האימות שלך. בבקשה נסה שוב מאוחר יותר. + digest_option: שלח התראות + digest_enum: + NONE: מיד nl_NL: talk-plugin-notifications: settings_title: Notificaties diff --git a/plugins/talk-plugin-notifications/server/translations.yml b/plugins/talk-plugin-notifications/server/translations.yml index d063ab522..c84523327 100644 --- a/plugins/talk-plugin-notifications/server/translations.yml +++ b/plugins/talk-plugin-notifications/server/translations.yml @@ -12,20 +12,6 @@ ar: confirm: "أكد" are_unsubscribed: "أنت الآن غير مشترك في جميع الإشعارات." token_invalid: "رابط إلغاء الاشتراك غير صالح ، انقر الرابط من بريد إلكتروني أحدث أو قم بزيارة جدول تعليقات وتسجيل الدخول لتغيير تفضيلات الإشعارات الخاصة بك" -en: - talk-plugin-notifications: - templates: - digest: - subject: "Your latest comment activity at {0}" - footer: "You received this notification because you are a commenter on {0} and you opted in to receive notifications." - links: - unsubscribe: "Unsubscribe from comment notifications" - unsubscribe_page: - unsubscribe: "Unsubscribe from comment notifications" - click_to_confirm: "Click below to confirm that you would like to unsubscribe from all notifications" - confirm: "Confirm" - are_unsubscribed: "You are now unsubscribed from all notifications." - token_invalid: "Unsubscribe link is invalid, click the link from a more recent email or visit a comment stream and login to change your notification preferences" de: talk-plugin-notifications: templates: @@ -40,6 +26,48 @@ de: confirm: "Bestätigen" are_unsubscribed: "Sie haben haben alle Benachrichtigungen erfolgreich abbestellt." token_invalid: "Der Abbestell-Link ist ungültig. Klicken Sie den Link einer neueren E-Mail oder gehen Sie zu einem Kommentarbereich, melden Sie sich an und ändern Sie dort Ihre Benachrichtigungseinstellungen" +en: + talk-plugin-notifications: + templates: + digest: + subject: "Your latest comment activity at {0}" + footer: "You received this notification because you are a commenter on {0} and you opted in to receive notifications." + links: + unsubscribe: "Unsubscribe from comment notifications" + unsubscribe_page: + unsubscribe: "Unsubscribe from comment notifications" + click_to_confirm: "Click below to confirm that you would like to unsubscribe from all notifications" + confirm: "Confirm" + are_unsubscribed: "You are now unsubscribed from all notifications." + token_invalid: "Unsubscribe link is invalid, click the link from a more recent email or visit a comment stream and login to change your notification preferences" +es: + talk-plugin-notifications: + templates: + digest: + subject: "Tu última actividad de comentarios en {0}" + footer: "Recibiste esta notificación porque eres un comentarista en {0} y aceptaste recibir notificaciones." + links: + unsubscribe: "Darse de baja de las notificaciones de comentarios" + unsubscribe_page: + unsubscribe: "Darse de baja de las notificaciones de comentarios" + click_to_confirm: "Haga clic a continuación para confirmar que desea cancelar la suscripción de todas las notificaciones" + confirm: "Confirmar" + are_unsubscribed: "Se ha cancelado la suscripción a todas las notificaciones." + token_invalid: "El enlace para cancelar la suscripción no es válido, haga clic en el enlace de un correo electrónico más reciente o visite un flujo de comentarios e inicie sesión para cambiar sus preferencias de notificación" +he: + talk-plugin-notifications: + templates: + digest: + subject: "פעילות התגובה האחרונה שלך ב- {0}" + footer: "קיבלת את ההודעה הזו מפני שאתה מפרסם על {0} ואתה הצטרפת לקבל התראות." + links: + unsubscribe: "בטל את הרישום להודעות על תגובות" + unsubscribe_page: + unsubscribe: "בטל את הרישום להודעות על תגובות" + click_to_confirm: "לחץ למטה כדי לאשר שברצונך לבטל את הרישום לכל ההתראות" + confirm: "אשר" + are_unsubscribed: "כעת אינך רשום לכל ההודעות." + token_invalid: "הקישור לביטול רישום אינו חוקי, לחץ על הקישור מאימייל עדכני יותר או בקר בזרם תגובות והתחבר כדי לשנות את העדפות ההתראה שלך" nl_NL: talk-plugin-notifications: templates: diff --git a/plugins/talk-plugin-offtopic/client/translations.json b/plugins/talk-plugin-offtopic/client/translations.json index c923c7c17..947cdc561 100644 --- a/plugins/talk-plugin-offtopic/client/translations.json +++ b/plugins/talk-plugin-offtopic/client/translations.json @@ -6,15 +6,15 @@ "da": { "off_topic": "Off Topic", "hide_off_topic": "Hide Off-Topic Comments" + }, + "de": { + "off_topic": "Off-Topic", + "hide_off_topic": "Off-Topic-Kommentare verbergen" }, "en": { "off_topic": "Off Topic", "hide_off_topic": "Hide Off-Topic Comments" }, - "de": { - "off_topic": "Off-Topic", - "hide_off_topic": "Off-Topic-Kommentare verbergen" - }, "es": { "off_topic": "Fuera de Tópico", "hide_off_topic": "Hide Off-Topic Comments" @@ -23,6 +23,10 @@ "off_topic": "Off Topic", "hide_off_topic": "Hide Off-Topic Comments" }, + "he": { + "off_topic": "מחוץ לנושא", + "hide_off_topic": "הסתר תגובות מחוץ לנושא" + }, "nl_NL": { "off_topic": "Off topic", "hide_off_topic": "Off-topic reacties verbergen" @@ -31,6 +35,10 @@ "off_topic": "Off Topic", "hide_off_topic": "Hide Off-Topic Comments" }, + "sr": { + "off_topic": "Van teme", + "hide_off_topic": "Sakri komentare van teme" + }, "zh_CN": { "off_topic": "无关主题", "hide_off_topic": "隐藏与主题无关评论" diff --git a/plugins/talk-plugin-permalink/client/components/PermalinkButton.js b/plugins/talk-plugin-permalink/client/components/PermalinkButton.js index 5916b7f17..e2c286436 100644 --- a/plugins/talk-plugin-permalink/client/components/PermalinkButton.js +++ b/plugins/talk-plugin-permalink/client/components/PermalinkButton.js @@ -36,7 +36,18 @@ export default class PermalinkButton extends React.Component { }; copyPermalink = () => { - this.permalinkInput.select(); + const iOS = navigator && navigator.userAgent.match(/ipad|iphone/i); + + if (iOS) { + let range = document.createRange(); + range.selectNodeContents(this.permalinkInput); + let selection = window.getSelection(); + selection.removeAllRanges(); + selection.addRange(range); + this.permalinkInput.setSelectionRange(0, 999999); + } else { + this.permalinkInput.select(); + } try { document.execCommand('copy'); this.setState({ diff --git a/plugins/talk-plugin-profile-data/client/components/DownloadCommentHistory.js b/plugins/talk-plugin-profile-data/client/components/DownloadCommentHistory.js index 17aa10264..26134e1e7 100644 --- a/plugins/talk-plugin-profile-data/client/components/DownloadCommentHistory.js +++ b/plugins/talk-plugin-profile-data/client/components/DownloadCommentHistory.js @@ -54,7 +54,9 @@ class DownloadCommentHistory extends Component { const canRequestDownload = !lastAccountDownloadDate || hoursLeft <= 0; return ( -
+

{t('download_request.section_title')}

{t('download_request.you_will_get_a_copy')}{' '} diff --git a/plugins/talk-plugin-profile-data/client/translations.yml b/plugins/talk-plugin-profile-data/client/translations.yml index f1330e3d6..a7519b5cb 100644 --- a/plugins/talk-plugin-profile-data/client/translations.yml +++ b/plugins/talk-plugin-profile-data/client/translations.yml @@ -49,108 +49,6 @@ ar: subtitle: "هل انت متأكد انك تريد حذف حسابك؟" description: "للتأكيد على رغبتك في حذف حسابك، يرجى كتابة العبارة التالية في مربع النص أدناه:" type_to_confirm: "اكتب عبارة أدناه للتأكيد" -en: - download_request: - section_title: "Download My Comment History" - you_will_get_a_copy: "You will receive an email with a link to download your comment history. You can make" - download_rate: "one download request every {0} days" - most_recent_request: "Your most recent request" - request: "Request Comment History" - rate_limit: "You can submit another Comment History request in {0}" - hours: "{0} hours" - days: "{0} days" - hour: "{0} hour" - day: "{0} day" - download_preparing: "Account Download Preparing - Check your email shortly for a download link" - delete_request: - account_deletion_cancelled: 'Account Deletion Request Cancelled - Your request to delete your account has been cancelled.' - account_deletion_requested: 'Account Deletion Requested' - received_on: "A request to delete your account was received on " - cancel_request_description: "If you would like to reactivate your account, you may cancel your request to delete your account below" - before: "before" - cancel_account_deletion_request: "Cancel Account Deletion Request" - delete_my_account: "Delete My Account" - delete_my_account_description: "Deleting your account will permanently erase your profile and remove all your comments from this site." - already_submitted_request_description: "You have already submitted a request to delete your account. Your account will be deleted after {0}. You may cancel the request until that time" - your_request_submitted_description: "Your request has been submitted and confirmation has been sent to the email address associated with your account." - your_account_deletion_scheduled: "Your account is scheduled to be deleted after:" - changed_your_mind: "Changed your mind?" - simply_go_to: "Simply go to your account again before this time and click" - tell_us_why: "Tell us why" - feedback_copy: "We would like to know why you chose to delete your account. Send us feedback on our comment system by emailing" - done: "Done" - cancel: "Cancel" - proceed: "Proceed" - input_is_not_correct: "The input is not correct" - step_0: - you_are_attempting: "You are attempting to delete your account. This means:" - item_1: "All of your comments are removed from this site" - item_2: "All of your comments are deleted from our database" - item_3: "Your username and email address are removed from our system" - step_1: - subtitle: "When will my account be deleted?" - description: "Your account will be deleted {0} hours after your request has been submitted." - subtitle_2: "Can I still write comments until my account is deleted?" - description_2: "Yes, you can still comment, reply, and react to comments until the {0} hours expires." - step_2: - description: "Before your account is deleted, we recommend you download your comment history for your records. After your account is deleted, you will be unable to request your comment history." - to_download: "To download your comment history go to:" - path: "My Profile > Download My Comment History" - step_3: - subtitle: "Are you sure you want to delete your account?" - description: "To confirm you would like to delete your account please type in the following phrase into the text box below:" - type_to_confirm: "Type phrase below to confirm" -es: - download_request: - section_title: "Descargar el historial de mis comentarios" - you_will_get_a_copy: "Recibirás un correo electrónico con un enlace para descargar tu historial de comentarios. Puedes hacer" - download_rate: "una solicitud de descarga cada {0} días" - most_recent_request: "Tu solicitud más reciente" - request: "Solicitar historial de comentarios" - rate_limit: "Puedes enviar otra solicitud de historial de comentarios en {0}" - hours: "{0} horas" - days: "{0} dias" - hour: "{0} hora" - day: "{0} día" - download_preparing: "Preparando para la descarga de la cuenta - Revises tu correo electrónico en breve para obtener un enlace de descarga" - delete_request: - account_deletion_cancelled: 'Solicitud de eliminación de cuenta cancelada - Tu solicitud para eliminar tu cuenta ha sido cancelada.' - account_deletion_requested: 'Eliminación de cuenta solicitada' - received_on: "Se recibió una solicitud para eliminar tu cuenta en " - cancel_request_description: "Si deseas reactivar tu cuenta, puedes cancelar tu solicitud para eliminar tu cuenta a continuación" - before: "antes" - cancel_account_deletion_request: "Cancelar solicitud de eliminación de cuenta" - delete_my_account: "Borrar mi cuenta" - delete_my_account_description: "Al eliminar tu cuenta, se borrará definitivamente tu perfil y se eliminarán todos tus comentarios de este sitio." - already_submitted_request_description: "Ya ha enviado una solicitud para eliminar tu cuenta. Tu cuenta será eliminada después de {0}. Puedes cancelar la solicitud hasta ese momento." - your_request_submitted_description: "Tu solicitud ha sido enviada y la confirmación ha sido enviada a la dirección de correo electrónico asociada con tu cuenta." - your_account_deletion_scheduled: "Tu cuenta está programada para ser eliminada después de:" - changed_your_mind: "¿Has cambiado de opinión?" - simply_go_to: "Simplemente acceda a su cuenta nuevamente antes de esta hora y haga clic en" - tell_us_why: "Dinos por qué" - feedback_copy: "Nos gustaría saber por qué eligió eliminar tu cuenta. Envíenos tus comentarios sobre nuestro sistema de comentarios por correo electrónico" - done: "Hecho" - cancel: "Cancelar" - proceed: "Proceder" - input_is_not_correct: "La entrada no es correcta" - step_0: - you_are_attempting: "Estás intentando eliminar tu cuenta. Esto significa:" - item_1: "Todos tus comentarios son eliminados de este sitio" - item_2: "Todos tus comentarios son eliminados de nuestra base de datos" - item_3: "Tu nombre de usuario y dirección de correo electrónico se eliminan de nuestro sistema" - step_1: - subtitle: "¿Cuándo se eliminará mi cuenta?" - description: "Tu cuenta se eliminará {0} horas después de que se hayas enviado su solicitud." - subtitle_2: "¿Todavía puedo escribir comentarios hasta que mi cuenta se elimine?" - description_2: "Sí, todavía puedes comentar, responder y reaccionar a los comentarios hasta que expire {0} horas." - step_2: - description: "Antes de que se elimine tu cuenta, te recomendamos que descargues su historial de comentarios para tus registros. Después de que se elimine su cuenta, no podrás solicitar su historial de comentarios." - to_download: "Para descargar tu historial de comentarios, ve a:" - path: "Mi perfil > Descargar historial de mis comentarios" - step_3: - subtitle: "¿Seguro que quieres eliminar tu cuenta?" - description: "Para confirmar que deseas eliminar tu cuenta, escriba la siguiente frase en el cuadro de texto a continuación:" - type_to_confirm: "Escriba la frase a continuación para confirmar" de: download_request: section_title: "Mein Kommentar-Archiv herunterladen" @@ -202,6 +100,210 @@ de: subtitle: "Sind Sie sicher, dass Sie Ihr Benutzerkonto löschen möchten?" description: "Um zu bestätigen, dass Sie Ihr Konto löschen möchten, geben Sie bitte folgende Zeichen in das Textfeld ein:" type_to_confirm: "Zur Bestätigung Zeichenfolge eingeben" +en: + download_request: + section_title: "Download My Comment History" + you_will_get_a_copy: "You will receive an email with a link to download your comment history. You can make" + download_rate: "one download request every {0} days" + most_recent_request: "Your most recent request" + request: "Request Comment History" + rate_limit: "You can submit another Comment History request in {0}" + hours: "{0} hours" + days: "{0} days" + hour: "{0} hour" + day: "{0} day" + download_preparing: "Account Download Preparing - Check your email shortly for a download link" + delete_request: + account_deletion_cancelled: 'Account Deletion Request Cancelled - Your request to delete your account has been cancelled.' + account_deletion_requested: 'Account Deletion Requested' + received_on: "A request to delete your account was received on " + cancel_request_description: "If you would like to reactivate your account, you may cancel your request to delete your account below" + before: "before" + cancel_account_deletion_request: "Cancel Account Deletion Request" + delete_my_account: "Delete My Account" + delete_my_account_description: "Deleting your account will permanently erase your profile and remove all your comments from this site." + already_submitted_request_description: "You have already submitted a request to delete your account. Your account will be deleted after {0}. You may cancel the request until that time" + your_request_submitted_description: "Your request has been submitted and confirmation has been sent to the email address associated with your account." + your_account_deletion_scheduled: "Your account is scheduled to be deleted after:" + changed_your_mind: "Changed your mind?" + simply_go_to: "Simply go to your account again before this time and click" + tell_us_why: "Tell us why" + feedback_copy: "We would like to know why you chose to delete your account. Send us feedback on our comment system by emailing" + done: "Done" + cancel: "Cancel" + proceed: "Proceed" + input_is_not_correct: "The input is not correct" + step_0: + you_are_attempting: "You are attempting to delete your account. This means:" + item_1: "All of your comments are removed from this site" + item_2: "All of your comments are deleted from our database" + item_3: "Your username and email address are removed from our system" + step_1: + subtitle: "When will my account be deleted?" + description: "Your account will be deleted {0} hours after your request has been submitted." + subtitle_2: "Can I still write comments until my account is deleted?" + description_2: "Yes, you can still comment, reply, and react to comments until the {0} hours expires." + step_2: + description: "Before your account is deleted, we recommend you download your comment history for your records. After your account is deleted, you will be unable to request your comment history." + to_download: "To download your comment history go to:" + path: "My Profile > Download My Comment History" + step_3: + subtitle: "Are you sure you want to delete your account?" + description: "To confirm you would like to delete your account please type in the following phrase into the text box below:" + type_to_confirm: "Type phrase below to confirm" +sr: + download_request: + section_title: "Download My Comment History" + you_will_get_a_copy: "You will receive an email with a link to download your comment history. You can make" + download_rate: "one download request every {0} days" + most_recent_request: "Your most recent request" + request: "Request Comment History" + rate_limit: "You can submit another Comment History request in {0}" + hours: "{0} hours" + days: "{0} days" + hour: "{0} hour" + day: "{0} day" + download_preparing: "Account Download Preparing - Check your email shortly for a download link" + delete_request: + account_deletion_cancelled: 'Account Deletion Request Cancelled - Your request to delete your account has been cancelled.' + account_deletion_requested: 'Account Deletion Requested' + received_on: "A request to delete your account was received on " + cancel_request_description: "If you would like to reactivate your account, you may cancel your request to delete your account below" + before: "before" + cancel_account_deletion_request: "Cancel Account Deletion Request" + delete_my_account: "Delete My Account" + delete_my_account_description: "Deleting your account will permanently erase your profile and remove all your comments from this site." + already_submitted_request_description: "You have already submitted a request to delete your account. Your account will be deleted after {0}. You may cancel the request until that time" + your_request_submitted_description: "Your request has been submitted and confirmation has been sent to the email address associated with your account." + your_account_deletion_scheduled: "Your account is scheduled to be deleted after:" + changed_your_mind: "Changed your mind?" + simply_go_to: "Simply go to your account again before this time and click" + tell_us_why: "Tell us why" + feedback_copy: "We would like to know why you chose to delete your account. Send us feedback on our comment system by emailing" + done: "Done" + cancel: "Cancel" + proceed: "Proceed" + input_is_not_correct: "The input is not correct" + step_0: + you_are_attempting: "You are attempting to delete your account. This means:" + item_1: "All of your comments are removed from this site" + item_2: "All of your comments are deleted from our database" + item_3: "Your username and email address are removed from our system" + step_1: + subtitle: "When will my account be deleted?" + description: "Your account will be deleted {0} hours after your request has been submitted." + subtitle_2: "Can I still write comments until my account is deleted?" + description_2: "Yes, you can still comment, reply, and react to comments until the {0} hours expires." + step_2: + description: "Before your account is deleted, we recommend you download your comment history for your records. After your account is deleted, you will be unable to request your comment history." + to_download: "To download your comment history go to:" + path: "My Profile > Download My Comment History" + step_3: + subtitle: "Are you sure you want to delete your account?" + description: "To confirm you would like to delete your account please type in the following phrase into the text box below:" + type_to_confirm: "Type phrase below to confirm" +es: + download_request: + section_title: "Descargar el historial de mis comentarios" + you_will_get_a_copy: "Recibirás un correo electrónico con un enlace para descargar tu historial de comentarios. Puedes hacer" + download_rate: "una solicitud de descarga cada {0} días" + most_recent_request: "Tu solicitud más reciente" + request: "Solicitar historial de comentarios" + rate_limit: "Puedes enviar otra solicitud de historial de comentarios en {0}" + hours: "{0} horas" + days: "{0} dias" + hour: "{0} hora" + day: "{0} día" + download_preparing: "Preparando para la descarga de la cuenta - Revises tu correo electrónico en breve para obtener un enlace de descarga" + delete_request: + account_deletion_cancelled: 'Solicitud de eliminación de cuenta cancelada - Tu solicitud para eliminar tu cuenta ha sido cancelada.' + account_deletion_requested: 'Eliminación de cuenta solicitada' + received_on: "Se recibió una solicitud para eliminar tu cuenta en " + cancel_request_description: "Si deseas reactivar tu cuenta, puedes cancelar tu solicitud para eliminar tu cuenta a continuación" + before: "antes" + cancel_account_deletion_request: "Cancelar solicitud de eliminación de cuenta" + delete_my_account: "Borrar mi cuenta" + delete_my_account_description: "Al eliminar tu cuenta, se borrará definitivamente tu perfil y se eliminarán todos tus comentarios de este sitio." + already_submitted_request_description: "Ya ha enviado una solicitud para eliminar tu cuenta. Tu cuenta será eliminada después de {0}. Puedes cancelar la solicitud hasta ese momento." + your_request_submitted_description: "Tu solicitud ha sido enviada y la confirmación ha sido enviada a la dirección de correo electrónico asociada con tu cuenta." + your_account_deletion_scheduled: "Tu cuenta está programada para ser eliminada después de:" + changed_your_mind: "¿Has cambiado de opinión?" + simply_go_to: "Simplemente acceda a su cuenta nuevamente antes de esta hora y haga clic en" + tell_us_why: "Dinos por qué" + feedback_copy: "Nos gustaría saber por qué eligió eliminar tu cuenta. Envíenos tus comentarios sobre nuestro sistema de comentarios por correo electrónico" + done: "Hecho" + cancel: "Cancelar" + proceed: "Proceder" + input_is_not_correct: "La entrada no es correcta" + step_0: + you_are_attempting: "Estás intentando eliminar tu cuenta. Esto significa:" + item_1: "Todos tus comentarios son eliminados de este sitio" + item_2: "Todos tus comentarios son eliminados de nuestra base de datos" + item_3: "Tu nombre de usuario y dirección de correo electrónico se eliminan de nuestro sistema" + step_1: + subtitle: "¿Cuándo se eliminará mi cuenta?" + description: "Tu cuenta se eliminará {0} horas después de que se hayas enviado su solicitud." + subtitle_2: "¿Todavía puedo escribir comentarios hasta que mi cuenta se elimine?" + description_2: "Sí, todavía puedes comentar, responder y reaccionar a los comentarios hasta que expire {0} horas." + step_2: + description: "Antes de que se elimine tu cuenta, te recomendamos que descargues su historial de comentarios para tus registros. Después de que se elimine su cuenta, no podrás solicitar su historial de comentarios." + to_download: "Para descargar tu historial de comentarios, ve a:" + path: "Mi perfil > Descargar historial de mis comentarios" + step_3: + subtitle: "¿Seguro que quieres eliminar tu cuenta?" + description: "Para confirmar que deseas eliminar tu cuenta, escriba la siguiente frase en el cuadro de texto a continuación:" + type_to_confirm: "Escriba la frase a continuación para confirmar" +he: + download_request: + section_title: "הורד את היסטוריית התגובות שלי" + you_will_get_a_copy: 'תקבל הודעת אימייל עם קישור להורדת היסטוריית התגובות שלך. ניתן לבצע' + download_rate: "בקשת הורדה אחת בכל {0} ימים" + most_recent_request: "הבקשה האחרונה שלך" + request: "בקש היסטוריה של תגובות" + rate_limit: "תוכל לשלוח בקשה נוספת של היסטוריית תגובות ב {0}" + hours: "{0} שעות" + days: "{0} ימים" + hour: "{0} שעה" + day: "{0} יום" + download_preparing: 'הורדה של החשבון שלך הכנה - בדוק את האימייל שלך בקרוב עבור קישור להורדה' + delete_request: + account_deletion_cancelled: 'בקשת מחיקת החשבון בוטלה - בקשתך למחוק את החשבון שלך בוטלה.' + account_deletion_requested: 'מחיקת חשבון מבוקשת' + received_on: "בקשה למחיקת החשבון התקבלה " + cancel_request_description: "אם תרצה להפעיל מחדש את חשבונך, תוכל לבטל את בקשתך למחוק את החשבון שלך למטה" + before: "לפני" + cancel_account_deletion_request: "ביטול בקשת מחיקת חשבון" + delete_my_account: "מחק את חשבוני" + delete_my_account_description: "מחיקת החשבון תמחק את הפרופיל שלך לצמיתות ותסיר את כל התגובות שלך מאתר זה." + already_submitted_request_description: "כבר שלחת בקשה למחיקת החשבון שלך. החשבון שלך יימחק לאחר {0}. תוכל לבטל את הבקשה עד למועד זה" + your_request_submitted_description: 'הבקשה שלך נשלחה ואישור נשלח לכתובת האימייל המשויכת לחשבונך.' + your_account_deletion_scheduled: "החשבון שלך אמור להימחק לאחר:" + changed_your_mind: "שינה את דעתך?" + simply_go_to: "פשוט עבור אל החשבון שלך שוב לפני זמן זה ולחץ" + tell_us_why: "ספר לנו למה" + feedback_copy: 'ברצוננו לדעת מדוע בחרת למחוק את החשבון שלך. שלח לנו משוב על מערכת התגובה שלנו באימייל' + done: "בוצע" + cancel: "בטל" + proceed: "להמשיך" + input_is_not_correct: "הקלט אינו נכון" + step_0: + you_are_attempting: "אתה מנסה למחוק את החשבון שלך. זה אומר:" + item_1: "כל התגובות שלך יוסרו מאתר זה" + item_2: "כל התשובות שלך נמחקות ממסד הנתונים שלנו" + item_3: "שם המשתמש וכתובת האימייל שלך מוסרים מהמערכת שלנו" + step_1: + subtitle: "מתי החשבון שלי יימחק?" + description: "החשבון שלך יימחק {0} שעות לאחר הגשת הבקשה." + subtitle_2: "האם אני עדיין יכול לכתוב תגובות עד למחיקת החשבון שלי?" + description_2: "כן, עדיין תוכל להגיב עד שפג תוקפו של {0} שעות." + step_2: + description: "לפני שחשבונך נמחק, אנו ממליצים לך להוריד את היסטוריית התגובות שלך עבור הרשומות שלך. לאחר מחיקת החשבון, לא תוכל לבקש את היסטוריית התגובות שלך." + to_download: "כדי להוריד את היסטוריית התגובות עבור אל:" + path: "הפרופיל שלי > הורד את ההיסטוריה של תגובות" + step_3: + subtitle: "האם אתה בטוח שברצונך למחוק את החשבון שלך?" + description: "כדי לאשר שברצונך למחוק את חשבונך, הקלד את הביטוי הבא בתיבת הטקסט שבהמשך:" + type_to_confirm: "הקלד ביטוי למטה כדי לאשר" pt_BR: download_request: section_title: "Baixar meu histórico de comentários" diff --git a/plugins/talk-plugin-profile-data/translations.yml b/plugins/talk-plugin-profile-data/translations.yml index 81de52452..4dbfb39f4 100644 --- a/plugins/talk-plugin-profile-data/translations.yml +++ b/plugins/talk-plugin-profile-data/translations.yml @@ -36,80 +36,6 @@ ar: body: "لقد ألغيت طلب حذف حسابك ل {0}. تم الآن إعادة تفعيل حسابك." error: DOWNLOAD_TOKEN_INVALID: "رابط التنزيل الخاص بك غير صالح." -en: - download_landing: - download_your_account: "Download Your Comment History" - download_details: "Your comment history will be downloaded into a .zip file. After your comment history is unzipped you will have a comma separated value (or .csv) file that you can easily import into your favorite spreadsheet application." - all_information_included: "For each of your comments the following information is included:" - information_included: - date: "When you wrote the comment" - url: "The permalink URL for the comment" - body: "The comment text" - asset_url: "The URL of the article or story where the comment appears" - confirm: "Download My Comment History" - email: - download: - subject: "Your comments are ready for download from {0}" - download_link_ready: "Click here to download your comments from {0} as of {1}:" - download_archive: "Download Archive" - delete: - subject: "Your account for {0} is scheduled to be deleted" - body: | - A request to delete your account was received. Your account is scheduled for deletion on {1}. - - After that time all of your comments will be removed from the site, all of your comments will be removed from our database, and your username and email address will be removed from our system. - - If you change your mind you can sign into your account and cancel the request before your scheduled account deletion time. - deleted: - subject: "Your account for {0} has been deleted" - body: | - Your commenter account for {0} is now deleted. We're sorry to see you go! - - If you'd like to re-join the discussion in the future, you can sign up for a new account. - - If you'd like to give us feedback on why you left and what we can do to make the commenting experience better, please email us at {1}. - cancelDelete: - subject: "Your account deletion request for {0} has been cancelled" - body: "You have cancelled your account deletion request for {0}. Your account is now reactivated." - error: - DOWNLOAD_TOKEN_INVALID: "Your download link is not valid." -es: - download_landing: - download_your_account: "Descargue su historial de comentarios" - download_details: "Su historial de comentarios se descargará en un archivo .zip. Después de descomprimir el historial de comentarios, tendrá un archivo de valores separados por comas (o .csv) que podrá importar fácilmente en su aplicación de hoja de cálculo favorita." - all_information_included: "Para cada uno de sus comentarios se incluye la siguiente información:" - information_included: - date: "Cuando escribiste el comentario" - url: "La URL del enlace permanente para el comentario" - body: "El texto de comentario" - asset_url: "La URL del artículo o historia donde aparece el comentario" - confirm: "Descargar el historial de mis comentarios" - email: - download: - subject: "Sus comentarios están listos para su descarga desde {0}" - download_link_ready: "Haga clic aquí para descargar sus comentarios de {0} a partir de {1}:" - download_archive: "Descargar archivo" - delete: - subject: "Su cuenta para {0} está programada para ser eliminada" - body: | - Se recibió una solicitud para eliminar su cuenta. Su cuenta está programada para ser eliminada {1}. - - Después de ese momento, todos sus comentarios serán eliminados del sitio, todos sus comentarios serán eliminados de nuestra base de datos y su nombre de usuario y dirección de correo electrónico serán eliminados de nuestro sistema. - - Si cambia de opinión, puede iniciar sesión en su cuenta y cancelar la solicitud antes de la hora programada para la eliminación de su cuenta. - deleted: - subject: "Su cuenta para {0} ha sido eliminada" - body: | - Su cuenta de comentarista para {0} ahora se borró. ¡Sentimos verte partir! - - Si desea volver a unirse a la discusión en el futuro, puede registrarse para obtener una nueva cuenta. - - Si desea darnos su opinión sobre por qué se fue y qué podemos hacer para mejorar la experiencia de comentarios, envíenos un correo electrónico a {1}. - cancelDelete: - subject: "La solicitud de eliminación de su cuenta para {0} ha sido cancelada" - body: "Ha cancelado la solicitud de eliminación de cuenta para {0}. Su cuenta ahora está reactivada." - error: - DOWNLOAD_TOKEN_INVALID: "Su enlace de descarga no es válido." de: download_landing: download_your_account: "Mein Kommentar-Archiv herunterladen" @@ -147,6 +73,154 @@ de: body: "Sie haben die Lösch-Anfrage für Ihr Benutzerkonto bei {0} abgebrochen. Das Konto ist nun wieder aktiv." error: DOWNLOAD_TOKEN_INVALID: "Der Download-Link ist ungültig." +en: + download_landing: + download_your_account: "Download Your Comment History" + download_details: "Your comment history will be downloaded into a .zip file. After your comment history is unzipped you will have a comma separated value (or .csv) file that you can easily import into your favorite spreadsheet application." + all_information_included: "For each of your comments the following information is included:" + information_included: + date: "When you wrote the comment" + url: "The permalink URL for the comment" + body: "The comment text" + asset_url: "The URL of the article or story where the comment appears" + confirm: "Download My Comment History" + email: + download: + subject: "Your comments are ready for download from {0}" + download_link_ready: "Click here to download your comments from {0} as of {1}:" + download_archive: "Download Archive" + delete: + subject: "Your account for {0} is scheduled to be deleted" + body: | + A request to delete your account was received. Your account is scheduled for deletion on {1}. + + After that time all of your comments will be removed from the site, all of your comments will be removed from our database, and your username and email address will be removed from our system. + + If you change your mind you can sign into your account and cancel the request before your scheduled account deletion time. + deleted: + subject: "Your account for {0} has been deleted" + body: | + Your commenter account for {0} is now deleted. We're sorry to see you go! + + If you'd like to re-join the discussion in the future, you can sign up for a new account. + + If you'd like to give us feedback on why you left and what we can do to make the commenting experience better, please email us at {1}. + cancelDelete: + subject: "Your account deletion request for {0} has been cancelled" + body: "You have cancelled your account deletion request for {0}. Your account is now reactivated." + error: + DOWNLOAD_TOKEN_INVALID: "Your download link is not valid." +sr: + download_landing: + download_your_account: "Preuzmi sve svoje komentare?" + download_details: "Svi vaši komentari biće preuzeti u .zip fajlu. Po raspakivanju kolekcije videćete .csv fajl sa vrednostima razdvojenim zarezima, i tkav fajl možete lako uvesti u svoj omiljeni spreadsheet program (Excel...)." + all_information_included: "Za svaki vaš komentar uključene su sledeće informacije:" + information_included: + date: "Kada ste komentar napisali" + url: "Permalink URL za komentar" + body: "Tekst komentara" + asset_url: "URL teksta/priče gde se komentar pojavljuje" + confirm: "Preuzmi kolekciju svojih komentara" + email: + download: + subject: "Vaši komnetari su spremni a preuzimanje sa {0}" + download_link_ready: "Kliknite ovde da preuzmete vaše komentare sa {0} od {1}:" + download_archive: "Preuzmi arhivu" + delete: + subject: "Vaš nalog za {0} je određen za brisanje" + body: | + Primili smo zahtev za brisanje vašeg naloga. Vaš nalog je određen za brisanje na {1}. + + Posle tog vremena svi vaši komentari biće uklonjeni sa sajta, svi vaši komnetari biće uklonjeni iz baze podataka, a vaše korisničko ime i e-mail uklonjeni sa našeg sistema. + + Ukoliko se predomislite možete se prijaviti na svoj nalog i poništiti zahtev pre predviđenog vremena za brisanje. + deleted: + subject: "Vaš nalog za {0} je obrisan" + body: | + Vaš nalog za {0} je sada obrisan. Žao nam je što idete! + + Ukoliko budete želeli da se ponovo uključite u rspravu u budućnosti, možete se prijaviti za novi nalog. + + If you'd like to give us feedback on why you left and what we can do to make the commenting experience better, please email us at {1}. + cancelDelete: + subject: "Zahtev za brisanje vašeg naloga {0} je otkazan" + body: "Poništili ste zahtev za brisanje naloga na {0}. Vaš nalog je reaktiviran." + error: + DOWNLOAD_TOKEN_INVALID: "Vaš link za preuzimanje nije validan." +es: + download_landing: + download_your_account: "Descargue su historial de comentarios" + download_details: "Su historial de comentarios se descargará en un archivo .zip. Después de descomprimir el historial de comentarios, tendrá un archivo de valores separados por comas (o .csv) que podrá importar fácilmente en su aplicación de hoja de cálculo favorita." + all_information_included: "Para cada uno de sus comentarios se incluye la siguiente información:" + information_included: + date: "Cuando escribiste el comentario" + url: "La URL del enlace permanente para el comentario" + body: "El texto de comentario" + asset_url: "La URL del artículo o historia donde aparece el comentario" + confirm: "Descargar el historial de mis comentarios" + email: + download: + subject: "Sus comentarios están listos para su descarga desde {0}" + download_link_ready: "Haga clic aquí para descargar sus comentarios de {0} a partir de {1}:" + download_archive: "Descargar archivo" + delete: + subject: "Su cuenta para {0} está programada para ser eliminada" + body: | + Se recibió una solicitud para eliminar su cuenta. Su cuenta está programada para ser eliminada {1}. + + Después de ese momento, todos sus comentarios serán eliminados del sitio, todos sus comentarios serán eliminados de nuestra base de datos y su nombre de usuario y dirección de correo electrónico serán eliminados de nuestro sistema. + + Si cambia de opinión, puede iniciar sesión en su cuenta y cancelar la solicitud antes de la hora programada para la eliminación de su cuenta. + deleted: + subject: "Su cuenta para {0} ha sido eliminada" + body: | + Su cuenta de comentarista para {0} ahora se borró. ¡Sentimos verte partir! + + Si desea volver a unirse a la discusión en el futuro, puede registrarse para obtener una nueva cuenta. + + Si desea darnos su opinión sobre por qué se fue y qué podemos hacer para mejorar la experiencia de comentarios, envíenos un correo electrónico a {1}. + cancelDelete: + subject: "La solicitud de eliminación de su cuenta para {0} ha sido cancelada" + body: "Ha cancelado la solicitud de eliminación de cuenta para {0}. Su cuenta ahora está reactivada." + error: + DOWNLOAD_TOKEN_INVALID: "Su enlace de descarga no es válido." +he: + download_landing: + download_your_account: "הורד היסטורית תגובתך" + download_details: "היסטוריית התגובה שלך תועבר לקובץ zip. לאחר היסטוריית התגובה שלך הוא unzipped יהיה לך ערך מופרדים בפסיק (או. CSV) קובץ ואתה יכול בקלות לייבא לתוך היישום האהוב עליך גיליון אלקטרוני." + all_information_included: "עבור כל אחת מהתגובות את המידע הבא נכלל:" + information_included: + date: "כאשר כתבת את התגובה" + url: "כתובת האתר של הקישור עבור התגובה" + body: "טקסט התגובה" + asset_url: "כתובת האתר של הכתבה שבו מופיעה ההערה" + confirm: "הורד היסטוריית תגובות" + email: + download: + subject: "התגובות שלך מוכנות להורדה מ {0}" + download_link_ready: "לחץ כאן כדי להוריד את התגובות שלך מתוך {0} החל מ- {1}:" + download_archive: "הורד ארכיון" + delete: + subject: "החשבון שלך עבור {0} אמור להימחק" + body: | + בקשה למחיקת החשבון שלך התקבלה. החשבון שלך מתוזמן למחיקה ב- {1}. + + לאחר מכן כל התגובות שלך יוסרו מהאתר, כל התגובות שלך יוסרו ממסד הנתונים שלנו, ושם המשתמש והכתובת האימייל שלך יוסרו מהמערכת שלנו. + + אם תשנה את דעתך, תוכל להיכנס לחשבון ולבטל את הבקשה לפני מועד מחיקת החשבון המתוכנן. + deleted: + subject: "החשבון שלך עבור {0} נמחק" + body: | + חשבון המפרסם שלך עבור {0} נמחק כעת. אנחנו מצטערים לראות אותך הולך! + + אם תרצה להצטרף מחדש לדיון בעתיד, תוכל להירשם לחשבון חדש. + + אם תרצה לתת לנו משוב על הסיבות לכך שעזבת ומה ניתן לעשות כדי לשפר את חווית התגובות, שלח לנו אימייל לכתובת {1}. + cancelDelete: + subject: "בקשת מחיקת החשבון שלך עבור {0} בוטלה" + body: "ביטלת את בקשת מחיקת החשבון שלך עבור {0}. החשבון שלך מופעל מחדש כעת." + error: + DOWNLOAD_TOKEN_INVALID: "קישור ההורדה שלך אינו חוקי." nl_NL: download_landing: download_your_account: "Download je reactie geschiedenis" @@ -184,4 +258,4 @@ nl_NL: subject: "Het verwijderen van je account {0} is ge-cancelled" body: "Je hebt het verzoek om je account {0} te verwijderen ingetrokken. Je account is nu actief." error: - DOWNLOAD_TOKEN_INVALID: "Je download link is ongeldig." + DOWNLOAD_TOKEN_INVALID: "Je download link is ongeldig." \ No newline at end of file diff --git a/plugins/talk-plugin-respect/client/translations.yml b/plugins/talk-plugin-respect/client/translations.yml index 9ea296491..669b9723d 100644 --- a/plugins/talk-plugin-respect/client/translations.yml +++ b/plugins/talk-plugin-respect/client/translations.yml @@ -5,15 +5,15 @@ ar: da: talk-plugin-respect: respect: Respect - respected: Respected -en: - talk-plugin-respect: - respect: Respect - respected: Respected + respected: Respected de: talk-plugin-respect: respect: Respekt respected: Respektiert +en: + talk-plugin-respect: + respect: Respect + respected: Respected es: talk-plugin-respect: respect: Respetar @@ -22,6 +22,10 @@ fr: talk-plugin-respect: respect: Respect respected: Respected +he: + talk-plugin-respect: + respect: כבד + respected: נכבד nl_NL: talk-plugin-respect: respect: Respect @@ -30,6 +34,10 @@ pt_BR: talk-plugin-respect: respect: Respeitar respected: Respeitado +sr: + talk-plugin-respect: + respect: Respekt + respected: Respektovan zh_CN: talk-plugin-respect: respect: "赞" diff --git a/plugins/talk-plugin-rich-text/client/translations.yml b/plugins/talk-plugin-rich-text/client/translations.yml index 321a6d5fc..4026a3382 100644 --- a/plugins/talk-plugin-rich-text/client/translations.yml +++ b/plugins/talk-plugin-rich-text/client/translations.yml @@ -8,4 +8,8 @@ nl_NL: format_bold: vetgedrukt format_italic: cursief format_blockquote: blockquote - +he: + talk-plugin-rich-text: + format_bold: מודגש + format_italic: נטוי + format_blockquote: blockquote \ No newline at end of file diff --git a/plugins/talk-plugin-sort-most-downvoted/client/translations.yml b/plugins/talk-plugin-sort-most-downvoted/client/translations.yml index 39dff599b..625564e5c 100644 --- a/plugins/talk-plugin-sort-most-downvoted/client/translations.yml +++ b/plugins/talk-plugin-sort-most-downvoted/client/translations.yml @@ -1,6 +1,9 @@ en: talk-plugin-sort-most-downvoted: label: Most downvoted first +he: + talk-plugin-sort-most-downvoted: + label: השלילי ביותר קודם nl_NL: talk-plugin-sort-most-downvoted: - label: Minst gerespecteerd eerst + label: Minst gerespecteerd eerst \ No newline at end of file diff --git a/plugins/talk-plugin-sort-most-liked/client/translations.yml b/plugins/talk-plugin-sort-most-liked/client/translations.yml index 710519f50..56984c602 100644 --- a/plugins/talk-plugin-sort-most-liked/client/translations.yml +++ b/plugins/talk-plugin-sort-most-liked/client/translations.yml @@ -3,25 +3,31 @@ ar: label: الأكثر إعجاباً أولاً da: talk-plugin-sort-most-liked: - label: Most liked first -en: - talk-plugin-sort-most-liked: - label: Most liked first + label: Most liked first de: talk-plugin-sort-most-liked: label: Beliebteste zuerst +en: + talk-plugin-sort-most-liked: + label: Most liked first es: talk-plugin-sort-most-liked: label: Más valoradas primero fr: talk-plugin-sort-most-liked: label: Most liked first +he: + talk-plugin-sort-most-liked: + label: הכי לייקד קודם nl_NL: talk-plugin-sort-most-liked: label: Meest geliked eerst pt_BR: talk-plugin-sort-most-liked: label: Most liked first +sr: + talk-plugin-sort-most-liked: + label: Prvo sa najviše sviđanja zh_CN: talk-plugin-sort-most-liked: label: "最被喜欢在前" diff --git a/plugins/talk-plugin-sort-most-loved/client/translations.yml b/plugins/talk-plugin-sort-most-loved/client/translations.yml index f9bf60d18..c8c258cd9 100644 --- a/plugins/talk-plugin-sort-most-loved/client/translations.yml +++ b/plugins/talk-plugin-sort-most-loved/client/translations.yml @@ -3,25 +3,31 @@ ar: label: الأكثر حبً أولاً da: talk-plugin-sort-most-loved: - label: Most loved first -en: - talk-plugin-sort-most-loved: - label: Most loved first + label: Most loved first de: talk-plugin-sort-most-loved: label: Häufigste "Ich liebe es" zuerst +en: + talk-plugin-sort-most-loved: + label: Most loved first es: talk-plugin-sort-most-loved: label: Más amadas primero fr: talk-plugin-sort-most-loved: label: Most loved first +he: + talk-plugin-sort-most-loved: + label: הכי אהוב קודם nl_NL: talk-plugin-sort-most-loved: label: Meest geliefd eerst pt_BR: talk-plugin-sort-most-loved: label: Most loved first +sr: + talk-plugin-sort-most-loved: + label: Prvo najvoljeniji zh_CN: talk-plugin-sort-most-loved: label: "最多被比心在前" diff --git a/plugins/talk-plugin-sort-most-replied/client/translations.yml b/plugins/talk-plugin-sort-most-replied/client/translations.yml index 5778ee3ea..cbeca22c5 100644 --- a/plugins/talk-plugin-sort-most-replied/client/translations.yml +++ b/plugins/talk-plugin-sort-most-replied/client/translations.yml @@ -3,25 +3,31 @@ ar: label: الأكثر ردودً أولاً da: talk-plugin-sort-most-replied: - label: Most replied first -en: - talk-plugin-sort-most-replied: - label: Most replied first + label: Most replied first de: talk-plugin-sort-most-replied: label: Häufigste Antworten zuerst +en: + talk-plugin-sort-most-replied: + label: Most replied first es: talk-plugin-sort-most-replied: label: Más respondidas primero fr: talk-plugin-sort-most-replied: label: Most replied first +he: + talk-plugin-sort-most-replied: + label: הכי השיב קודם nl_NL: talk-plugin-sort-most-replied: label: Meest beantwoord eerst pt_BR: talk-plugin-sort-most-replied: label: Mais respondidos primeiro +sr: + talk-plugin-sort-most-replied: + label: Prvo sa najviše odgovora zh_CN: talk-plugin-sort-most-replied: label: "最多回复在前" diff --git a/plugins/talk-plugin-sort-most-respected/client/translations.yml b/plugins/talk-plugin-sort-most-respected/client/translations.yml index 50a87b9db..b462c05c8 100644 --- a/plugins/talk-plugin-sort-most-respected/client/translations.yml +++ b/plugins/talk-plugin-sort-most-respected/client/translations.yml @@ -4,24 +4,30 @@ ar: da: talk-plugin-sort-most-respected: label: Most respected first -en: - talk-plugin-sort-most-respected: - label: Most respected first de: talk-plugin-sort-most-respected: label: Häufigste "Respektiert" zuerst +en: + talk-plugin-sort-most-respected: + label: Most respected first es: talk-plugin-sort-most-respected: label: Más respetadas primero fr: talk-plugin-sort-most-respected: label: Most respected first +he: + talk-plugin-sort-most-respected: + label: הכי מכובד קודם nl_NL: talk-plugin-sort-most-respected: label: Meest gerespecteerd eerst pt_BR: talk-plugin-sort-most-respected: label: Mais respeitados primeiro +sr: + talk-plugin-sort-most-respected: + label: Prvo najpopularniji zh_CN: talk-plugin-sort-most-respected: label: "最多被赞在前" diff --git a/plugins/talk-plugin-sort-most-upvoted/client/translations.yml b/plugins/talk-plugin-sort-most-upvoted/client/translations.yml index 7c16572b7..1f5e55121 100644 --- a/plugins/talk-plugin-sort-most-upvoted/client/translations.yml +++ b/plugins/talk-plugin-sort-most-upvoted/client/translations.yml @@ -1,12 +1,15 @@ -en: - talk-plugin-sort-most-upvoted: - label: Most upvoted first de: talk-plugin-sort-most-upvoted: label: Am besten bewertet +en: + talk-plugin-sort-most-upvoted: + label: Most upvoted first es: talk-plugin-sort-oldest: label: Más votadas primero +he: + talk-plugin-sort-most-upvoted: + label: החיובי ביותר קודם nl_NL: talk-plugin-sort-most-upvoted: - label: Meest gerespecteerd eerst + label: Meest gerespecteerd eerst \ No newline at end of file diff --git a/plugins/talk-plugin-sort-newest/client/translations.yml b/plugins/talk-plugin-sort-newest/client/translations.yml index 63bdb024d..dd1caf5df 100644 --- a/plugins/talk-plugin-sort-newest/client/translations.yml +++ b/plugins/talk-plugin-sort-newest/client/translations.yml @@ -3,25 +3,31 @@ ar: label: الأحدث أولاً da: talk-plugin-sort-newest: - label: Newest first -en: - talk-plugin-sort-newest: - label: Newest first + label: Newest first de: talk-plugin-sort-newest: label: Neueste zuerst +en: + talk-plugin-sort-newest: + label: Newest first es: talk-plugin-sort-newest: label: Más nuevas primero fr: talk-plugin-sort-newest: label: Newest first +he: + talk-plugin-sort-newest: + label: מהראשונה לאחרונה nl_NL: talk-plugin-sort-newest: label: Nieuwste eerst pt_BR: talk-plugin-sort-newest: label: Mais novos primeiro +sr: + talk-plugin-sort-newest: + label: Prvo najnoviji zh_CN: talk-plugin-sort-newest: label: "最新发表在前" diff --git a/plugins/talk-plugin-sort-oldest/client/translations.yml b/plugins/talk-plugin-sort-oldest/client/translations.yml index 59a97c9d8..1dc5fe3cf 100644 --- a/plugins/talk-plugin-sort-oldest/client/translations.yml +++ b/plugins/talk-plugin-sort-oldest/client/translations.yml @@ -3,25 +3,31 @@ ar: label: الأقدم أولاً da: talk-plugin-sort-oldest: - label: Oldest first -en: - talk-plugin-sort-oldest: - label: Oldest first + label: Oldest first de: talk-plugin-sort-oldest: label: Älteste zuerst +en: + talk-plugin-sort-oldest: + label: Oldest first es: talk-plugin-sort-oldest: label: Más viejas primero fr: talk-plugin-sort-oldest: label: Oldest first +he: + talk-plugin-sort-oldest: + label: מהאחרונה לראשונה nl_NL: talk-plugin-sort-oldest: label: Oudste eerst pt_BR: talk-plugin-sort-oldest: label: Mais antigos primeiro +sr: + talk-plugin-sort-oldest: + label: Prvo najstariji zh_CN: talk-plugin-sort-oldest: label: "最早发表在前" diff --git a/plugins/talk-plugin-subscriber/client/translations.yml b/plugins/talk-plugin-subscriber/client/translations.yml index 4cf482978..c6ea68d72 100644 --- a/plugins/talk-plugin-subscriber/client/translations.yml +++ b/plugins/talk-plugin-subscriber/client/translations.yml @@ -4,18 +4,21 @@ ar: da: talk-plugin-subscriber: subscriber: "Subscriber" -en: - talk-plugin-subscriber: - subscriber: "Subscriber" de: talk-plugin-subscriber: subscriber: "Abonnent" +en: + talk-plugin-subscriber: + subscriber: "Subscriber" es: talk-plugin-subscriber: subscriber: "Subscriptor" fr: talk-plugin-subscriber: subscriber: "Subscriber" +he: + talk-plugin-subscriber: + subscriber: "מנוי" nl_NL: talk-plugin-subscriber: subscriber: "Abonnee" diff --git a/plugins/talk-plugin-toxic-comments/README.md b/plugins/talk-plugin-toxic-comments/README.md index f158a5eb3..e649f5832 100644 --- a/plugins/talk-plugin-toxic-comments/README.md +++ b/plugins/talk-plugin-toxic-comments/README.md @@ -28,3 +28,4 @@ Configuration: [ms](https://www.npmjs.com/package/ms). (Default `300ms`) - `TALK_PERSPECTIVE_DO_NOT_STORE` - Whether the API stores or deletes the comment text and context from this request after it has been evaluated. Stored comments will be used for future research and community model building purposes to improve the API over time. (Default `true`) [Perspective API - Analyze Comment Request](https://github.com/conversationai/perspectiveapi/blob/master/api_reference.md#analyzecomment-request) - `TALK_PERSPECTIVE_SEND_FEEDBACK` - If set to `TRUE`, this plugin will send back moderation actions as feedback to [Perspective](http://perspectiveapi.com/) to improve their model. (Default `FALSE`) +- `TALK_PERSPECTIVE_MODEL` - Determines the Perspective API toxicity model that should be used, i.e. `TOXICITY` vs `SEVERE_TOXICITY`. A list of available models provided by the Perspective API can be found [here](https://github.com/conversationai/perspectiveapi/blob/master/api_reference.md#models). When displaying the toxicity score, this model will be used by default. If this model isn't available on the comment metadata (such as when the model has been changed), it will fall back to the stored `TOXICITY` model as Talk will always fetch that. (Default `SEVERE_TOXICITY`) diff --git a/plugins/talk-plugin-toxic-comments/client/translations.yml b/plugins/talk-plugin-toxic-comments/client/translations.yml index 4151b6d7c..cde2888a6 100644 --- a/plugins/talk-plugin-toxic-comments/client/translations.yml +++ b/plugins/talk-plugin-toxic-comments/client/translations.yml @@ -31,25 +31,7 @@ da: flags: reasons: comment: - toxic_comment: "Highly likely to be Toxic" -en: - error: - COMMENT_IS_TOXIC: | - Are you sure? The language in this comment might violate our community guidelines. - You can edit the comment or submit it for moderator review. - talk-plugin-toxic-comments: - unlikely: "Unlikely" - highly_likely: "Highly Likely" - possibly: "Possibly" - likely: "Likely" - toxic_comment: "Toxic Comment" - still_toxic: | - This edited comment might still violate our community guidelines. - Our moderation team will review your comment shortly. - flags: - reasons: - comment: - toxic_comment: "Highly likely to be Toxic" + toxic_comment: "Highly likely to be Toxic" de: error: COMMENT_IS_TOXIC: | @@ -68,6 +50,24 @@ de: reasons: comment: toxic_comment: "Höchstwahrscheinlich bösartig" +en: + error: + COMMENT_IS_TOXIC: | + Are you sure? The language in this comment might violate our community guidelines. + You can edit the comment or submit it for moderator review. + talk-plugin-toxic-comments: + unlikely: "Unlikely" + highly_likely: "Highly Likely" + possibly: "Possibly" + likely: "Likely" + toxic_comment: "Toxic Comment" + still_toxic: | + This edited comment might still violate our community guidelines. + Our moderation team will review your comment shortly. + flags: + reasons: + comment: + toxic_comment: "Highly likely to be Toxic" es: error: COMMENT_IS_TOXIC: | @@ -104,6 +104,22 @@ fr: reasons: comment: toxic_comment: "Highly likely to be Toxic" +he: + error: + COMMENT_IS_TOXIC: | + האם אתה בטוח? השפה שבתגובה זו עלולה להפר את הנחיות הקהילה שלנו. תוכל לערוך את ההערה או לשלוח אותה לביקורת של המתווך. + talk-plugin-toxic-comments: + unlikely: "לא סביר" + highly_likely: "סביר מאוד" + possibly: "יתכן" + likely: "סביר" + toxic_comment: "תגובה רעילה" + still_toxic: | + תגובה זו עדיין עשויה להפר את הנחיות הקהילה שלנו. צוות הפיקוח שלנו יבחן את ההערה שלך בקרוב. + flags: + reasons: + comment: + toxic_comment: "סביר מאוד להיות רעיל" nl_NL: error: COMMENT_IS_TOXIC: | @@ -140,6 +156,24 @@ pt_BR: reasons: comment: toxic_comment: "Highly likely to be Toxic" +sr: + error: + COMMENT_IS_TOXIC: | + Da li ste sigurni? Jezik u ovom komentaru može narušavati naša pravila diskusije. + Pregledajte komentar, ili ga svejdno pošaljite moderatoru na odobrenje. + talk-plugin-toxic-comments: + unlikely: "Malo verovatno" + highly_likely: "Vrlo verovatno" + possibly: "Moguće" + likely: "Verovatno" + toxic_comment: "Toksičan komentar" + still_toxic: | + Ovako preuređen komentar još uvek može narušavati naša pravila diskusije. + Naš moderatorski tim će uskro pregledati vaš komentar. + flags: + reasons: + comment: + toxic_comment: "Vrlo verovatno toksičan" zh_CN: error: COMMENT_IS_TOXIC: "您确定吗?此评论的表述可能违反了我们的社群指引方针。您可以编辑评论,或直接提交让管理人员审查。" diff --git a/plugins/talk-plugin-toxic-comments/server/config.js b/plugins/talk-plugin-toxic-comments/server/config.js index cc0c2daa0..1987049cb 100644 --- a/plugins/talk-plugin-toxic-comments/server/config.js +++ b/plugins/talk-plugin-toxic-comments/server/config.js @@ -9,6 +9,7 @@ const config = { API_TIMEOUT: ms(process.env.TALK_PERSPECTIVE_TIMEOUT || '300ms'), DO_NOT_STORE: process.env.TALK_PERSPECTIVE_DO_NOT_STORE || true, SEND_FEEDBACK: process.env.TALK_PERSPECTIVE_SEND_FEEDBACK === 'TRUE', + API_MODEL: process.env.TALK_PERSPECTIVE_MODEL || 'SEVERE_TOXICITY', }; if (process.env.NODE_ENV !== 'test' && !config.API_KEY) { diff --git a/plugins/talk-plugin-toxic-comments/server/perspective.js b/plugins/talk-plugin-toxic-comments/server/perspective.js index db6d0c8de..773c097fb 100644 --- a/plugins/talk-plugin-toxic-comments/server/perspective.js +++ b/plugins/talk-plugin-toxic-comments/server/perspective.js @@ -5,8 +5,10 @@ const { THRESHOLD, API_TIMEOUT, DO_NOT_STORE, + API_MODEL, } = require('./config'); const debug = require('debug')('talk:plugin:toxic-comments'); +const get = require('lodash/get'); // Load the global Talk configuration, we want to grab some variables.. const { ROOT_URL } = require('config'); @@ -55,7 +57,7 @@ async function getScores(text) { doNotStore: DO_NOT_STORE, requestedAttributes: { TOXICITY: {}, - SEVERE_TOXICITY: {}, + [API_MODEL]: {}, }, }); if (!data || data.error) { @@ -64,7 +66,7 @@ async function getScores(text) { TOXICITY: { summaryScore: null, }, - SEVERE_TOXICITY: { + [API_MODEL]: { summaryScore: null, }, }; @@ -74,20 +76,21 @@ async function getScores(text) { TOXICITY: { summaryScore: data.attributeScores.TOXICITY.summaryScore.value, }, - SEVERE_TOXICITY: { - summaryScore: data.attributeScores.SEVERE_TOXICITY.summaryScore.value, + [API_MODEL]: { + summaryScore: data.attributeScores[API_MODEL].summaryScore.value, }, }; } /** - * Get toxicity probability + * Get toxicity probability from the scores, trying first the selection model, + * but falling back to the `TOXICITY` model when the selected model isn't found. * * @param {object} scores scores as returned by `getScores` * @return {number} toxicity probability from 0 - 1.0 */ function getProbability(scores) { - return scores.SEVERE_TOXICITY.summaryScore; + return get(scores, API_MODEL, scores.TOXICITY).summaryScore; } /** diff --git a/plugins/talk-plugin-toxic-comments/server/resolvers.js b/plugins/talk-plugin-toxic-comments/server/resolvers.js index 8143b446e..5108c07bc 100644 --- a/plugins/talk-plugin-toxic-comments/server/resolvers.js +++ b/plugins/talk-plugin-toxic-comments/server/resolvers.js @@ -1,8 +1,15 @@ const get = require('lodash/get'); +const { API_MODEL } = require('./config'); module.exports = { Comment: { toxicity: comment => - get(comment, 'metadata.perspective.SEVERE_TOXICITY.summaryScore'), + // Try to get the score from the custom model first, but fall back to the + // TOXICITY score if it isn't provided. + get( + comment, + `metadata.perspective.${API_MODEL}.summaryScore`, + get(comment, 'metadata.perspective.TOXICITY.summaryScore') + ), }, }; diff --git a/plugins/talk-plugin-viewing-options/client/translations.yml b/plugins/talk-plugin-viewing-options/client/translations.yml index 903033af8..825e7440e 100644 --- a/plugins/talk-plugin-viewing-options/client/translations.yml +++ b/plugins/talk-plugin-viewing-options/client/translations.yml @@ -7,17 +7,17 @@ da: talk-plugin-viewing-options: viewing_options: "Viewing Options" sort: Sorting - filter: Filtering -en: - talk-plugin-viewing-options: - viewing_options: "Viewing Options" - sort: Sorting - filter: Filtering + filter: Filtering de: talk-plugin-viewing-options: viewing_options: "Ansichtsoptionen" sort: Sortieren filter: Filtern +en: + talk-plugin-viewing-options: + viewing_options: "Viewing Options" + sort: Sorting + filter: Filtering es: talk-plugin-viewing-options: viewing_options: "Opciones" @@ -28,6 +28,11 @@ fr: viewing_options: "Viewing Options" sort: Sorting filter: Filtering +he: + talk-plugin-viewing-options: + viewing_options: "אפשרויות צפייה" + sort: מיון + filter: סינון nl_NL: talk-plugin-viewing-options: viewing_options: "Weergave opties" @@ -38,6 +43,11 @@ pt_BR: viewing_options: "Ver opções" sort: Sorting filter: Filtering +sr: + talk-plugin-viewing-options: + viewing_options: "Opcije pregleda" + sort: Sortiranje + filter: Filterisanje zh_CN: talk-plugin-viewing-options: viewing_options: "查看选项" diff --git a/routes/api/v1/assets.js b/routes/api/v1/assets.js index 6e3d4266d..a8b043946 100644 --- a/routes/api/v1/assets.js +++ b/routes/api/v1/assets.js @@ -1,4 +1,5 @@ const express = require('express'); +const Joi = require('joi'); const router = express.Router(); const authorization = require('../../../middleware/authorization'); const { ErrHTTPNotFound } = require('../../../errors'); @@ -31,36 +32,63 @@ const FilterOpenAssets = (query, filter) => { } }; +const ListAssetsSchema = Joi.object({ + value: Joi.string() + .empty('') + .default(''), + field: Joi.string() + .empty('') + .default('publication_date'), + page: Joi.number() + .empty('') + .default(1) + .min(1), + asc: Joi.bool() + .empty('') + .default(false), + filter: Joi.string() + .empty('') + .valid(['all', 'open', 'closed']) + .default('all'), + limit: Joi.number() + .empty('') + .default(20) + .max(500) + .min(0), +}); + // List assets. router.get( '/', authorization.needed('ADMIN', 'MODERATOR'), async (req, res, next) => { - const { - value = '', - field = 'publication_date', - page = 1, - asc = 'false', - filter = 'all', - limit = 20, - } = req.query; + const { value: query, error: err } = Joi.validate( + req.query, + ListAssetsSchema, + {} + ); + if (err) { + return next(err); + } + + let { value, field, page, asc, filter, limit } = query; try { - const order = asc === 'true' ? 1 : -1; + const order = asc ? 1 : -1; const queryOpts = { sort: { [field]: order, created_at: order }, skip: (page - 1) * limit, - limit, + limit: limit, }; // Find all the assets. let [result, count] = await Promise.all([ - // Find the actuall assets. + // Find the actual assets. FilterOpenAssets(AssetsService.search({ value }), filter) .sort(queryOpts.sort) - .skip(parseInt(queryOpts.skip)) - .limit(parseInt(queryOpts.limit)) + .skip(queryOpts.skip) + .limit(queryOpts.limit) .lean(), // Get the count of actual assets. @@ -70,9 +98,9 @@ router.get( // Send back the asset data. res.json({ result, - limit: Number(limit), + limit, count, - page: Number(page), + page, totalPages: Math.ceil(count / (limit === 0 ? 1 : limit)), }); } catch (e) { diff --git a/services/moderation/phases/premod.js b/services/moderation/phases/premod.js index db1a3fd1b..36aba634e 100644 --- a/services/moderation/phases/premod.js +++ b/services/moderation/phases/premod.js @@ -9,9 +9,9 @@ module.exports = ( }, } ) => { - // If the settings say that we're in premod mode, then the comment is in - // premod status. - if (moderation === 'PRE') { + // If the settings say that we're in premod mode, or the user is flagged as + // always premod, then the comment is in premod status. + if (moderation === 'PRE' || ctx.user.status.alwaysPremod.status === true) { return { status: 'PREMOD', }; diff --git a/services/users.js b/services/users.js index 2ce0b7c15..e85ed67a2 100644 --- a/services/users.js +++ b/services/users.js @@ -207,6 +207,50 @@ class Users { return user; } + static async setAlwaysPremodStatus(id, status, assignedBy = null) { + let user = await User.findOneAndUpdate( + { + id, + 'status.alwaysPremod.status': { + $ne: status, + }, + }, + { + $set: { + 'status.alwaysPremod.status': status, + }, + $push: { + 'status.alwaysPremod.history': { + status, + assigned_by: assignedBy, + created_at: Date.now(), + }, + }, + }, + { + new: true, + runValidators: true, + } + ); + + if (!user) { + user = await User.findOne({ id }); + if (!user) { + throw new ErrNotFound(); + } + + if (user.status.alwaysPremod.status === status) { + return user; + } + + throw new Error( + 'always premod status change edit failed for an unknown reason' + ); + } + + return user; + } + static async setBanStatus(id, status, assignedBy = null, message) { let user = await User.findOneAndUpdate( { diff --git a/webpack.config.js b/webpack.config.js index e23aa1e2a..322ad8317 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -169,6 +169,7 @@ const config = { TALK_THREADING_LEVEL: '3', TALK_ADDTL_COMMENTS_ON_LOAD_MORE: '10', TALK_ASSET_COMMENTS_LOAD_DEPTH: '10', + TALK_ADDTL_REPLIES_ON_LOAD_MORE: '999999', TALK_REPLY_COMMENTS_LOAD_DEPTH: '3', TALK_DEFAULT_STREAM_TAB: 'all', TALK_DEFAULT_LANG: 'en',