Commit d15017ff authored by George Goldberg's avatar George Goldberg
Browse files

Merge branch 'master' into advanced-permissions-phase-2

parents cbcf0593 ec3e625a
......@@ -12,3 +12,13 @@ export function setModalSearchTerm(term) {
return {data: true};
};
}
export function setSystemUsersSearch(term, team = '') {
return async (dispatch) => {
dispatch({
type: SearchTypes.SET_SYSTEM_USERS_SEARCH,
data: {term, team},
});
return {data: true};
};
}
......@@ -32,6 +32,7 @@ import UserStore from 'stores/user_store.jsx';
import WebSocketClient from 'client/web_websocket_client.jsx';
import {loadPlugin, loadPluginsIfNecessary, removePlugin} from 'plugins';
import {ActionTypes, Constants, ErrorBarTypes, Preferences, SocketEvents, UserStatuses} from 'utils/constants.jsx';
import {fromAutoResponder} from 'utils/post_utils';
import {getSiteURL} from 'utils/url.jsx';
import * as WebrtcActions from './webrtc_actions.jsx';
......@@ -311,7 +312,7 @@ function handleNewPostEvent(msg) {
getProfilesAndStatusesForPosts([post], dispatch, getState);
if (post.user_id !== UserStore.getCurrentId()) {
if (post.user_id !== UserStore.getCurrentId() && !fromAutoResponder(post)) {
UserStore.setStatus(post.user_id, UserStatuses.ONLINE);
}
}
......
......@@ -2,18 +2,17 @@
// See LICENSE.txt for license information.
import {connect} from 'react-redux';
import {getConfig, getLicense} from 'mattermost-redux/selectors/entities/general';
import {getConfig} from 'mattermost-redux/selectors/entities/general';
import {getPasswordConfig} from 'utils/utils.jsx';
import ResetPasswordModal from './reset_password_modal.jsx';
function mapStateToProps(state) {
const license = getLicense(state);
const config = getConfig(state);
return {
passwordConfig: getPasswordConfig(license, config),
passwordConfig: getPasswordConfig(config),
};
}
......
......@@ -7,6 +7,10 @@ import {getTeams, getTeamStats} from 'mattermost-redux/actions/teams';
import {getUser, getUserAccessToken} from 'mattermost-redux/actions/users';
import {getTeamsList} from 'mattermost-redux/selectors/entities/teams';
import {getConfig, getLicense} from 'mattermost-redux/selectors/entities/general';
import {Stats} from 'mattermost-redux/constants';
import {setSystemUsersSearch} from 'actions/views/search';
import {SearchUserTeamFilter} from 'utils/constants.jsx';
import SystemUsers from './system_users.jsx';
......@@ -20,10 +24,32 @@ function mapStateToProps(state) {
const enableUserAccessTokens = config.EnableUserAccessTokens === 'true';
const experimentalEnableAuthenticationTransfer = config.ExperimentalEnableAuthenticationTransfer === 'true';
const search = state.views.search.systemUsersSearch;
let totalUsers = 0;
let searchTerm = '';
let teamId = '';
if (search) {
searchTerm = search.term || '';
teamId = search.team || '';
if (!teamId || teamId === SearchUserTeamFilter.ALL_USERS) {
const stats = state.entities.admin.analytics || {[Stats.TOTAL_USERS]: 0, [Stats.TOTAL_INACTIVE_USERS]: 0};
totalUsers = stats[Stats.TOTAL_USERS] + stats[Stats.TOTAL_INACTIVE_USERS];
} else if (teamId === SearchUserTeamFilter.NO_TEAM) {
totalUsers = 0;
} else {
const stats = state.entities.teams.stats[teamId] || {total_member_count: 0};
totalUsers = stats.total_member_count;
}
}
return {
teams: getTeamsList(state),
siteName,
mfaEnabled,
totalUsers,
searchTerm,
teamId,
enableUserAccessTokens,
experimentalEnableAuthenticationTransfer,
};
......@@ -36,6 +62,7 @@ function mapDispatchToProps(dispatch) {
getTeamStats,
getUser,
getUserAccessToken,
setSystemUsersSearch,
}, dispatch),
};
}
......
......@@ -8,17 +8,12 @@ import {FormattedMessage} from 'react-intl';
import {getStandardAnalytics} from 'actions/admin_actions.jsx';
import {reloadIfServerVersionChanged} from 'actions/global_actions.jsx';
import {loadProfiles, loadProfilesAndTeamMembers, loadProfilesWithoutTeam, searchUsers} from 'actions/user_actions.jsx';
import AnalyticsStore from 'stores/analytics_store.jsx';
import TeamStore from 'stores/team_store.jsx';
import UserStore from 'stores/user_store.jsx';
import {Constants, StatTypes, UserSearchOptions} from 'utils/constants.jsx';
import {Constants, UserSearchOptions, SearchUserTeamFilter} from 'utils/constants.jsx';
import * as Utils from 'utils/utils.jsx';
import SystemUsersList from './list';
const ALL_USERS = '';
const NO_TEAM = 'no_team';
const USER_ID_LENGTH = 26;
const USERS_PER_PAGE = 50;
......@@ -49,6 +44,9 @@ export default class SystemUsers extends React.Component {
* Whether or not the experimental authentication transfer is enabled.
*/
experimentalEnableAuthenticationTransfer: PropTypes.bool.isRequired,
totalUsers: PropTypes.number.isRequired,
searchTerm: PropTypes.string.isRequired,
teamId: PropTypes.string.isRequired,
actions: PropTypes.shape({
......@@ -71,14 +69,13 @@ export default class SystemUsers extends React.Component {
* Function to get a user access token
*/
getUserAccessToken: PropTypes.func.isRequired,
setSystemUsersSearch: PropTypes.func.isRequired,
}).isRequired,
}
constructor(props) {
super(props);
this.updateTotalUsersFromStore = this.updateTotalUsersFromStore.bind(this);
this.loadDataForTeam = this.loadDataForTeam.bind(this);
this.loadComplete = this.loadComplete.bind(this);
......@@ -93,64 +90,30 @@ export default class SystemUsers extends React.Component {
this.renderFilterRow = this.renderFilterRow.bind(this);
this.state = {
totalUsers: AnalyticsStore.getAllSystem()[StatTypes.TOTAL_USERS],
teamId: ALL_USERS,
term: '',
loading: true,
searching: false,
};
}
componentDidMount() {
AnalyticsStore.addChangeListener(this.updateTotalUsersFromStore);
TeamStore.addStatsChangeListener(this.updateTotalUsersFromStore);
this.loadDataForTeam(this.state.teamId);
this.loadDataForTeam(this.props.teamId);
this.props.actions.getTeams(0, 1000).then(reloadIfServerVersionChanged);
}
UNSAFE_componentWillUpdate(nextProps, nextState) { // eslint-disable-line camelcase
const nextTeamId = nextState.teamId;
if (this.state.teamId !== nextTeamId) {
this.updateTotalUsersFromStore(nextTeamId);
this.loadDataForTeam(nextTeamId);
}
}
componentWillUnmount() {
AnalyticsStore.removeChangeListener(this.updateTotalUsersFromStore);
TeamStore.removeStatsChangeListener(this.updateTotalUsersFromStore);
}
updateTotalUsersFromStore(teamId = this.state.teamId) {
if (teamId === ALL_USERS) {
this.setState({
totalUsers: AnalyticsStore.getAllSystem()[StatTypes.TOTAL_USERS],
});
} else if (teamId === NO_TEAM) {
this.setState({
totalUsers: 0,
});
} else {
this.setState({
totalUsers: TeamStore.getStats(teamId).total_member_count,
});
}
this.props.actions.setSystemUsersSearch('', '');
}
loadDataForTeam(teamId) {
if (this.state.term) {
this.search(this.state.term, teamId);
if (this.props.searchTerm) {
this.search(this.props.searchTerm, teamId);
return;
}
if (teamId === ALL_USERS) {
if (teamId === SearchUserTeamFilter.ALL_USERS) {
loadProfiles(0, Constants.PROFILE_CHUNK_SIZE, this.loadComplete);
getStandardAnalytics();
} else if (teamId === NO_TEAM) {
} else if (teamId === SearchUserTeamFilter.NO_TEAM) {
loadProfilesWithoutTeam(0, Constants.PROFILE_CHUNK_SIZE, this.loadComplete);
} else {
loadProfilesAndTeamMembers(0, Constants.PROFILE_CHUNK_SIZE, teamId, this.loadComplete);
......@@ -163,26 +126,28 @@ export default class SystemUsers extends React.Component {
}
handleTeamChange(e) {
this.setState({teamId: e.target.value});
const teamId = e.target.value;
this.loadDataForTeam(teamId);
this.props.actions.setSystemUsersSearch(this.props.searchTerm, teamId);
}
handleTermChange(term) {
this.setState({term});
this.props.actions.setSystemUsersSearch(term, this.props.teamId);
}
nextPage(page) {
// Paging isn't supported while searching
if (this.state.teamId === ALL_USERS) {
if (this.props.teamId === SearchUserTeamFilter.ALL_USERS) {
loadProfiles(page + 1, USERS_PER_PAGE, this.loadComplete);
} else if (this.state.teamId === NO_TEAM) {
} else if (this.props.teamId === SearchUserTeamFilter.NO_TEAM) {
loadProfilesWithoutTeam(page + 1, USERS_PER_PAGE, this.loadComplete);
} else {
loadProfilesAndTeamMembers(page + 1, USERS_PER_PAGE, this.state.teamId, this.loadComplete);
loadProfilesAndTeamMembers(page + 1, USERS_PER_PAGE, this.props.teamId, this.loadComplete);
}
}
search(term, teamId = this.state.teamId) {
search(term, teamId = this.props.teamId) {
if (term === '') {
this.setState({
loading: false,
......@@ -197,14 +162,13 @@ export default class SystemUsers extends React.Component {
doSearch(teamId, term, now = false) {
clearTimeout(this.searchTimeoutId);
this.term = term;
this.setState({loading: true});
const options = {
[UserSearchOptions.ALLOW_INACTIVE]: true,
};
if (teamId === NO_TEAM) {
if (teamId === SearchUserTeamFilter.NO_TEAM) {
options[UserSearchOptions.WITHOUT_TEAM] = true;
}
......@@ -251,7 +215,6 @@ export default class SystemUsers extends React.Component {
const {data} = await this.props.actions.getUserAccessToken(id);
if (data) {
this.term = data.user_id;
this.setState({term: data.user_id});
this.getUserById(data.user_id);
return;
......@@ -294,10 +257,10 @@ export default class SystemUsers extends React.Component {
<select
className='form-control system-users__team-filter'
onChange={this.handleTeamChange}
value={this.state.teamId}
value={this.props.teamId}
>
<option value={ALL_USERS}>{Utils.localizeMessage('admin.system_users.allUsers', 'All Users')}</option>
<option value={NO_TEAM}>{Utils.localizeMessage('admin.system_users.noTeams', 'No Teams')}</option>
<option value={SearchUserTeamFilter.ALL_USERS}>{Utils.localizeMessage('admin.system_users.allUsers', 'All Users')}</option>
<option value={SearchUserTeamFilter.NO_TEAM}>{Utils.localizeMessage('admin.system_users.noTeams', 'No Teams')}</option>
{teams}
</select>
</label>
......@@ -324,10 +287,10 @@ export default class SystemUsers extends React.Component {
search={this.search}
nextPage={this.nextPage}
usersPerPage={USERS_PER_PAGE}
total={this.state.totalUsers}
total={this.props.totalUsers}
teams={this.props.teams}
teamId={this.state.teamId}
term={this.state.term}
teamId={this.props.teamId}
term={this.props.searchTerm}
onTermChange={this.handleTermChange}
mfaEnabled={this.props.mfaEnabled}
enableUserAccessTokens={this.props.enableUserAccessTokens}
......
......@@ -30,7 +30,9 @@ function onChannelByIdentifierEnter({match, history}) {
if (identifier.length === LENGTH_OF_ID) {
// It's hard to tell an ID apart from a channel name of the same length, so check first if
// the identifier matches a channel that we have
if (ChannelStore.getByName(identifier)) {
const channelsByName = ChannelStore.getByName(identifier);
const moreChannelsByName = ChannelStore.getMoreChannelsList().find((chan) => chan.name === identifier);
if (channelsByName || moreChannelsByName) {
goToChannelByChannelName(match, history);
} else {
goToChannelByChannelId(match, history);
......
......@@ -2,14 +2,13 @@
// See LICENSE.txt for license information.
import {connect} from 'react-redux';
import {getLicense, getConfig} from 'mattermost-redux/selectors/entities/general';
import {getConfig} from 'mattermost-redux/selectors/entities/general';
import {getPasswordConfig} from 'utils/utils.jsx';
import ClaimController from './claim_controller.jsx';
function mapStateToProps(state) {
const license = getLicense(state);
const config = getConfig(state);
const siteName = config.SiteName;
const ldapLoginFieldName = config.LdapLoginFieldName;
......@@ -17,7 +16,7 @@ function mapStateToProps(state) {
return {
siteName,
ldapLoginFieldName,
passwordConfig: getPasswordConfig(license, config),
passwordConfig: getPasswordConfig(config),
};
}
......
......@@ -66,6 +66,7 @@ export default class AbstractIncomingWebhook extends React.Component {
displayName: hook.display_name || '',
description: hook.description || '',
channelId: hook.channel_id || '',
channelLocked: hook.channel_locked || false,
username: hook.username || '',
iconURL: hook.icon_url || '',
saving: false,
......@@ -103,6 +104,7 @@ export default class AbstractIncomingWebhook extends React.Component {
const hook = {
channel_id: this.state.channelId,
channel_locked: this.state.channelLocked,
display_name: this.state.displayName,
description: this.state.description,
username: this.state.username,
......@@ -130,6 +132,12 @@ export default class AbstractIncomingWebhook extends React.Component {
});
}
updateChannelLocked = (e) => {
this.setState({
channelLocked: e.target.checked,
});
}
updateUsername = (e) => {
this.setState({
username: e.target.value,
......@@ -245,6 +253,31 @@ export default class AbstractIncomingWebhook extends React.Component {
</div>
</div>
</div>
<div className='form-group'>
<label
className='control-label col-sm-4'
htmlFor='channelLocked'
>
<FormattedMessage
id='add_incoming_webhook.channelLocked'
defaultMessage='Lock to this channel'
/>
</label>
<div className='col-md-5 col-sm-8 checkbox'>
<input
id='channelLocked'
type='checkbox'
checked={this.state.channelLocked}
onChange={this.updateChannelLocked}
/>
<div className='form__help'>
<FormattedMessage
id='add_incoming_webhook.channelLocked.help'
defaultMessage='If set, the incoming webhook can only post to the channel selected above.'
/>
</div>
</div>
</div>
{ this.props.enablePostUsernameOverride &&
<div className='form-group'>
<label
......
......@@ -352,7 +352,7 @@ export default class AbstractOutgoingWebhook extends React.Component {
<div className='form__help'>
<FormattedMessage
id='add_outgoing_webhook.channel.help'
defaultMessage='Public channel to receive webhook payloads. Optional if at least one Trigger Word is specified.'
defaultMessage='Public channel that delivers payload to webhook. Optional if at least one Trigger Word is specified.'
/>
</div>
</div>
......
......@@ -30,7 +30,7 @@ function mapStateToProps(state) {
customBrand,
enableCustomBrand,
customDescriptionText,
passwordConfig: getPasswordConfig(license, config),
passwordConfig: getPasswordConfig(config),
};
}
......
......@@ -23,6 +23,7 @@ import {
getUser,
searchProfiles,
} from 'mattermost-redux/selectors/entities/users';
import * as ChannelActions from 'mattermost-redux/actions/channels';
import GlobeIcon from 'components/svg/globe_icon';
import LockIcon from 'components/svg/lock_icon';
......@@ -226,13 +227,12 @@ export default class SwitchChannelProvider extends Provider {
usersAsync = Client4.autocompleteUsers(channelPrefix, '', '');
}
const channelsAsync = Client4.searchChannels(teamId, channelPrefix);
let usersFromServer = [];
let channelsFromServer = [];
try {
usersFromServer = await usersAsync;
channelsFromServer = await channelsAsync;
const {data} = await ChannelActions.searchChannels(teamId, channelPrefix)(store.dispatch, store.getState);
channelsFromServer = data;
} catch (err) {
AppDispatcher.handleServerAction({
type: ActionTypes.RECEIVED_ERROR,
......
......@@ -129,7 +129,7 @@ export default class SystemNotice extends React.PureComponent {
onClick={this.hideAndRemind}
>
<FormattedMessage
id='system_notice_remind_me'
id='system_notice.remind_me'
defaultMessage='Remind me later'
/>
</button>
......@@ -139,7 +139,7 @@ export default class SystemNotice extends React.PureComponent {
onClick={this.hideAndForget}
>
<FormattedMessage
id='system_notice_dont_show'
id='system_notice.dont_show'
defaultMessage="Don't show again"
/>
</button>
......
......@@ -46,7 +46,7 @@ function mapStateToProps(state, ownProps) {
enableSaml,
enableSignUpWithOffice365,
experimentalEnableAuthenticationTransfer,
passwordConfig: getPasswordConfig(license, config),
passwordConfig: getPasswordConfig(config),
};
}
......
......@@ -376,6 +376,8 @@
"admin.email.enableEmailBatching.siteURL": "E-Mail-Stapelverarbeitung kann nicht aktiviert werden, solange SiteURL nicht in <b>Konfiguration > SiteURL</b> konfiguriert ist.",
"admin.email.enableEmailBatchingDesc": "Wenn wahr, werden Benutzer E-Mail-Benachrichtigungen für mehrere Direktnachrichten und Erwähnungen kombiniert in einer einzigen Mail erhalten. Die Abarbeitung wird in einem Standardintervall von 15 Minuten stattfinden, konfigurierbar in Kontoeinstellungen > Benachrichtigungen.",
"admin.email.enableEmailBatchingTitle": "E-Mail-Stapelverarbeitung aktivieren:",
"admin.email.enablePreviewModeBannerDescription": "When true, the Preview Mode banner is displayed so users are aware that email notifications are disabled. When false, the Preview Mode banner is not displayed to users.",
"admin.email.enablePreviewModeBannerTitle": "Enable Preview Mode Banner:",
"admin.email.enableSMTPAuthDesc": "Wenn aktiviert, werden Benutzername und Passwort für die Authentifizierung am SMTP-Server verwendet.",
"admin.email.enableSMTPAuthTitle": "SMTP-Authentifizierung aktivieren:",
"admin.email.fullPushNotification": "Senden eines Ausschnitts der vollständigen Nachricht",
......@@ -403,7 +405,7 @@
"admin.email.notificationOrganization": "Adresse in der Fußzeile von Benachrichtigungen:",
"admin.email.notificationOrganizationDescription": "Name und Adresse der Organisation bzw. des Unternehmens, wie sie in E-Mail-Benachrichtigungen von Mattermost angezeigt werden sollen, z.B. \"© Musterfirma GmbH, Musterstraße 23, 59424 Musterhausen, Deutschland\". Wenn dieses Feld leer bleibt, werden der Name und die Adresse der Organisation nicht angezeigt.",
"admin.email.notificationOrganizationExample": "Z.B.: \"© Musterfirma GmbH, Musterstraße 23, 59424 Musterhausen, Deutschland\"",
"admin.email.notificationsDescription": "Normalerweise wahr in Produktionsumgebungen. Wenn wahr, versucht Mattermost E-Mail-Benachrichtigungen zu versenden. Entwickler sollten dies auf falsch für eine schnellere Entwicklung setzen.<br />Durch setzen auf wahr wird der Vorschaumodus-Banner entfernt (benötigt Ab- und Anmeldung nach Änderung).",
"admin.email.notificationsDescription": "Typically set to true in production. When true, Mattermost attempts to send email notifications. Developers may set this field to false to skip email setup for faster development.",
"admin.email.notificationsTitle": "Aktiviere E-Mail-Benachrichtigungen: ",
"admin.email.passwordSaltDescription": "32 Zeichen langer Salt der zum Signieren von Passwort zurücksetzen E-Mails hinzugefügt wird. Zufallsgeneriert bei Installation. \"Neu generieren\" klicken um einen neuen Salt zu erstellen.",
"admin.email.passwordSaltExample": "Z.B.: \"bjlSR4QqkXFBr7TP4oDzlfZmcNuH9Yo\"",
......@@ -780,6 +782,7 @@
"admin.plugin.activating": "Wird aktiviert...",
"admin.plugin.banner": "Plugins sind experimentell und sind nicht für die Verwendung in produktiven Umgebungen empfohlen.",
"admin.plugin.choose": "Datei auswählen",
"admin.plugin.cluster_instance": "Cluster Instance",
"admin.plugin.deactivate": "Deaktivieren",
"admin.plugin.deactivating": "Wird deaktiviert...",
"admin.plugin.desc": "Beschreibung:",
......@@ -790,18 +793,34 @@
"admin.plugin.installedTitle": "Installierte Plugins: ",
"admin.plugin.management.banner": "Plugins sind auf ihrem Server deaktiviert. Um sie zu aktivieren, gehen Sie zu <strong>Plugins > Konfiguration</strong>.",
"admin.plugin.management.title": "Verwaltung",
"admin.plugin.multiple_versions_warning": "There are multiple versions of this plugin installed across your cluster. Re-install this plugin to ensure it works consistently.",
"admin.plugin.name": "Name:",
"admin.plugin.no_plugins": "Keine Plugins installiert.",
"admin.plugin.prepackaged": "Vorpaketiert",
"admin.plugin.remove": "Entfernen",
"admin.plugin.removing": "Entferne...",
"admin.plugin.settingsButton": "Einstellungen",
"admin.plugin.state": "State",
"admin.plugin.state.failed_to_start": "Failed to start",
"admin.plugin.state.failed_to_start.description": "This plugin failed to start. Check your system logs for errors.",
"admin.plugin.state.failed_to_stay_running": "Crashing",
"admin.plugin.state.failed_to_stay_running.description": "This plugin crashed multiple times and is no longer running. Check your system logs for errors.",
"admin.plugin.state.not_running": "Not running",
"admin.plugin.state.not_running.description": "This plugin is not activated.",
"admin.plugin.state.running": "Running",
"admin.plugin.state.running.description": "This plugin is running.",
"admin.plugin.state.starting": "Starting",
"admin.plugin.state.starting.description": "This plugin is starting.",
"admin.plugin.state.stopping": "Stopping",
"admin.plugin.state.stopping.description": "This plugin is stopping.",
"admin.plugin.state.unknown": "unbekannt",
"admin.plugin.upload": "Hochladen",
"admin.plugin.uploadDesc": "Laden Sie ein Plugin für ihren Mattermost-Server hoch. Sehen Sie in der <a href=\"https://about.mattermost.com/default-plugin-uploads\" target=\"_blank\">Dokumentation</a> für mehr Details nach.",
"admin.plugin.uploadDisabledDesc": "Um Plugins hochladen zu können, gehen Sie zu <strong>Plugins > Konfiguration</strong>. Schauen Sie in die <a href=\"https://about.mattermost.com/default-plugin-uploads\" target=\"_blank\">Dokumentation</a>, um mehr zu erfahren.",
"admin.plugin.uploadTitle": "Lade Plugin hoch: ",
"admin.plugin.uploading": "Lade hoch...",
"admin.plugin.version": "Version:",
"admin.plugin.version_title": "Version",
"admin.plugins.settings.enable": "Plugins aktivieren: ",
"admin.plugins.settings.enableDesc": "Wenn wahr, werden Plugins auf ihrem Mattermost-Server aktiviert. Verwenden Sie Plugins zur Integration mit Drittanbietersystemen oder passen sie die Benutzeroberfläche ihres Mattermost-Servers an.Sehen Sie in der <a href=\"https://about.mattermost.com/default-plugins\" target=\"_blank\">Dokumentation</a> für mehr Details nach.",
"admin.plugins.settings.title": "Konfiguration",
......@@ -1530,7 +1549,7 @@
"combined_system_message.removed_from_team.one": "{firstUser} was <b>removed from the team</b>.",
"combined_system_message.removed_from_team.one_you": "You were <b>removed from the team</b>.",
"combined_system_message.removed_from_team.two": "{firstUser} and {secondUser} were <b>removed from the team</b>.",
"combined_system_message.you": "You",
"combined_system_message.you": "Sie",
"confirm_modal.cancel": "Abbrechen",
"connecting_screen": "Verbindung wird aufgebaut",
"convert_channel.cancel": "Nein, abbrechen",
......@@ -2125,6 +2144,26 @@
"mobile.client_upgrade.no_upgrade_subtitle": "Sie haben bereits die neueste Version.",
"mobile.client_upgrade.no_upgrade_title": "Ihre App ist aktuell",
"mobile.client_upgrade.upgrade": "Aktualisieren",
"mobile.combined_system_message.added_to_channel": "sie zu diesem Kanal hinzufügen",
"mobile.combined_system_message.added_to_team": "added to the team",
"mobile.combined_system_message.by_actor": " by {actor}.",
"mobile.combined_system_message.first_user": "{firstUser} ",
"mobile.combined_system_message.first_user_and": "{firstUser} and ",
"mobile.combined_system_message.first_user_and_second_user": "{firstUser} and {secondUser} ",
"mobile.combined_system_message.first_user_and_second_user_were": "{firstUser} and {secondUser} were ",
"mobile.combined_system_message.first_user_was": "{firstUser} was ",
"mobile.combined_system_message.joined_channel": "joined the channel",
"mobile.combined_system_message.joined_team": "joined the team",
"mobile.combined_system_message.left_channel": "left the channel",
"mobile.combined_system_message.left_team": "left the team",
"mobile.combined_system_message.others": "{numOthers} others ",
"mobile.combined_system_message.removed_from_channel": "removed from the channel",
"mobile.combined_system_message.removed_from_team": "removed from the team",
"mobile.combined_system_message.users_and_last_user": "{users} und {lastUser}",
"mobile.combined_system_message.users_and_last_user_were": "{users} und {lastUser}",
"mobile.combined_system_message.were": "were ",
"mobile.combined_system_message.you": "Sie",
"mobile.combined_system_message.you_were": "You were ",
"mobile.commands.error_title": "Fehler beim Ausführen des Befehls",
"mobile.components.channels_list_view.yourChannels": "Ihre Kanäle:",
"mobile.components.error_list.dismiss_all": "Alle verwerfen",
......@@ -2772,12 +2811,14 @@
"team_export_tab.unable": " Fehler beim Export: {error}",
"team_import_tab.failure": " Fehler beim Import: ",
"team_import_tab.import": "Importieren",
"team_import_tab.importHelpCliDocsLink": "CLI tool for Slack import",
"team_import_tab.importHelpDocsLink": "Dokumentation",
"team_import_tab.importHelpExportInstructions": "Slack > Administration > Workspace settings > Import/Export Data > Export > Start Export",
"team_import_tab.importHelpExporterLink": "Slack erweiterter Exporter",
"team_import_tab.importHelpLine1": "Slack-Import zu Mattermost unterstützt das Importieren von Nachrichten in den öffentlichen Kanälen Ihres Slack-Teams.",
"team_import_tab.importHelpLine2": "Um ein Slack Team zu importieren, gehen Sie zu {exportInstructions}. Schauen Sie in den {uploadDocsLink} um mehr darüber zu erfahren.",
"team_import_tab.importHelpLine3": "Um Nachrichten mit angehängten Dateien zu importieren, schauen Sie sich {slackAdvancedExporterLink} für mehr Details an.",
"team_import_tab.importHelpLine4": "For Slack teams with over 10,000 messages, we recommend using the {cliLink}.",
"team_import_tab.importSlack": "Import von Slack (Beta)",
"team_import_tab.importing": " Importiere...",
"team_import_tab.successful": " Import erfolgreich: ",
......@@ -3011,24 +3052,14 @@
"user.settings.notifications.commentsNever": "Keine Benachrichtigung bei Antworten auslösen, sofern ich nicht erwähnt wurde",
"user.settings.notifications.commentsRoot": "Löse Benachrichtigungen für Nachrichten in Diskussionssträngen aus die ich beginne",
"user.settings.notifications.desktop": "Desktop-Benachrichtigungen senden",