Merge branch 'develop' into chore.subscribe.to.settings

This commit is contained in:
Gerzon Z 2021-06-15 08:59:01 -04:00 committed by GitHub
commit 71e06ed442
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
131 changed files with 23380 additions and 12767 deletions

File diff suppressed because it is too large Load Diff

View File

@ -26,4 +26,14 @@
<item name="colorPrimaryDark">@color/splashBackground</item> <item name="colorPrimaryDark">@color/splashBackground</item>
<item name="android:navigationBarColor">@color/splashBackground</item> <item name="android:navigationBarColor">@color/splashBackground</item>
</style> </style>
<!-- https://github.com/facebook/react-native/blob/d1ab03235cb4b93304150878d2b9057ab45bba77/ReactAndroid/src/main/res/views/modal/values/themes.xml#L5 -->
<style name="Theme.FullScreenDialog">
<item name="android:windowNoTitle">true</item>
<item name="android:windowIsFloating">false</item>
<item name="android:windowBackground">@android:color/transparent</item>
<item name="android:windowDrawsSystemBarBackgrounds">true</item>
<item name="android:statusBarColor">@android:color/transparent</item>
<item name="android:navigationBarColor">@android:color/transparent</item>
</style>
</resources> </resources>

View File

@ -14,9 +14,10 @@ export function createChannelSuccess(data) {
}; };
} }
export function createChannelFailure(err) { export function createChannelFailure(err, isTeam) {
return { return {
type: types.CREATE_CHANNEL.FAILURE, type: types.CREATE_CHANNEL.FAILURE,
err err,
isTeam
}; };
} }

View File

@ -14,11 +14,12 @@ export function unsubscribeRoom(rid) {
}; };
} }
export function leaveRoom(rid, t) { export function leaveRoom(roomType, room, selected) {
return { return {
type: types.ROOM.LEAVE, type: types.ROOM.LEAVE,
rid, room,
t roomType,
selected
}; };
} }

View File

@ -0,0 +1,5 @@
export const MESSAGE_TYPE_LOAD_MORE = 'load_more';
export const MESSAGE_TYPE_LOAD_PREVIOUS_CHUNK = 'load_previous_chunk';
export const MESSAGE_TYPE_LOAD_NEXT_CHUNK = 'load_next_chunk';
export const MESSAGE_TYPE_ANY_LOAD = [MESSAGE_TYPE_LOAD_MORE, MESSAGE_TYPE_LOAD_PREVIOUS_CHUNK, MESSAGE_TYPE_LOAD_NEXT_CHUNK];

View File

@ -193,5 +193,8 @@ export default {
}, },
Allow_Save_Media_to_Gallery: { Allow_Save_Media_to_Gallery: {
type: 'valueAsBoolean' type: 'valueAsBoolean'
},
Accounts_AllowInvisibleStatusOption: {
type: 'valueAsString'
} }
}; };

View File

@ -1,6 +1,6 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { Text } from 'react-native'; import { Text, View } from 'react-native';
import { themes } from '../../constants/colors'; import { themes } from '../../constants/colors';
import { CustomIcon } from '../../lib/Icons'; import { CustomIcon } from '../../lib/Icons';
@ -18,14 +18,22 @@ export const Item = React.memo(({ item, hide, theme }) => {
onPress={onPress} onPress={onPress}
style={[styles.item, { backgroundColor: themes[theme].focusedBackground }]} style={[styles.item, { backgroundColor: themes[theme].focusedBackground }]}
theme={theme} theme={theme}
testID={item.testID}
> >
<CustomIcon name={item.icon} size={20} color={item.danger ? themes[theme].dangerColor : themes[theme].bodyText} /> <CustomIcon name={item.icon} size={20} color={item.danger ? themes[theme].dangerColor : themes[theme].bodyText} />
<Text <View style={styles.titleContainer}>
numberOfLines={1} <Text
style={[styles.title, { color: item.danger ? themes[theme].dangerColor : themes[theme].bodyText }]} numberOfLines={1}
> style={[styles.title, { color: item.danger ? themes[theme].dangerColor : themes[theme].bodyText }]}
{item.title} >
</Text> {item.title}
</Text>
</View>
{ item.right ? (
<View style={styles.rightContainer}>
{item.right ? item.right() : null}
</View>
) : null }
</Button> </Button>
); );
}); });
@ -34,7 +42,9 @@ Item.propTypes = {
title: PropTypes.string, title: PropTypes.string,
icon: PropTypes.string, icon: PropTypes.string,
danger: PropTypes.bool, danger: PropTypes.bool,
onPress: PropTypes.func onPress: PropTypes.func,
right: PropTypes.func,
testID: PropTypes.string
}), }),
hide: PropTypes.func, hide: PropTypes.func,
theme: PropTypes.string theme: PropTypes.string

View File

@ -22,6 +22,9 @@ export default StyleSheet.create({
content: { content: {
paddingTop: 16 paddingTop: 16
}, },
titleContainer: {
flex: 1
},
title: { title: {
fontSize: 16, fontSize: 16,
marginLeft: 16, marginLeft: 16,
@ -58,5 +61,8 @@ export default StyleSheet.create({
fontSize: 16, fontSize: 16,
...sharedStyles.textMedium, ...sharedStyles.textMedium,
...sharedStyles.textAlignCenter ...sharedStyles.textAlignCenter
},
rightContainer: {
paddingLeft: 12
} }
}); });

View File

@ -5,6 +5,7 @@ import PropTypes from 'prop-types';
import { themes } from '../../constants/colors'; import { themes } from '../../constants/colors';
import { CustomIcon } from '../../lib/Icons'; import { CustomIcon } from '../../lib/Icons';
import { withTheme } from '../../theme'; import { withTheme } from '../../theme';
import { ICON_SIZE } from './constants';
const styles = StyleSheet.create({ const styles = StyleSheet.create({
icon: { icon: {
@ -17,13 +18,15 @@ const ListIcon = React.memo(({
theme, theme,
name, name,
color, color,
style style,
testID
}) => ( }) => (
<View style={[styles.icon, style]}> <View style={[styles.icon, style]}>
<CustomIcon <CustomIcon
name={name} name={name}
color={color ?? themes[theme].auxiliaryText} color={color ?? themes[theme].auxiliaryText}
size={20} size={ICON_SIZE}
testID={testID}
/> />
</View> </View>
)); ));
@ -32,7 +35,8 @@ ListIcon.propTypes = {
theme: PropTypes.string, theme: PropTypes.string,
name: PropTypes.string, name: PropTypes.string,
color: PropTypes.string, color: PropTypes.string,
style: PropTypes.object style: PropTypes.object,
testID: PropTypes.string
}; };
ListIcon.displayName = 'List.Icon'; ListIcon.displayName = 'List.Icon';

View File

@ -10,8 +10,9 @@ import sharedStyles from '../../views/Styles';
import { withTheme } from '../../theme'; import { withTheme } from '../../theme';
import I18n from '../../i18n'; import I18n from '../../i18n';
import { Icon } from '.'; import { Icon } from '.';
import { BASE_HEIGHT, PADDING_HORIZONTAL } from './constants'; import { BASE_HEIGHT, ICON_SIZE, PADDING_HORIZONTAL } from './constants';
import { withDimensions } from '../../dimensions'; import { withDimensions } from '../../dimensions';
import { CustomIcon } from '../../lib/Icons';
const styles = StyleSheet.create({ const styles = StyleSheet.create({
container: { container: {
@ -34,7 +35,15 @@ const styles = StyleSheet.create({
flex: 1, flex: 1,
justifyContent: 'center' justifyContent: 'center'
}, },
textAlertContainer: {
flexDirection: 'row',
alignItems: 'center'
},
alertIcon: {
paddingLeft: 4
},
title: { title: {
flexShrink: 1,
fontSize: 16, fontSize: 16,
...sharedStyles.textRegular ...sharedStyles.textRegular
}, },
@ -50,7 +59,7 @@ const styles = StyleSheet.create({
}); });
const Content = React.memo(({ const Content = React.memo(({
title, subtitle, disabled, testID, left, right, color, theme, translateTitle, translateSubtitle, showActionIndicator, fontScale title, subtitle, disabled, testID, left, right, color, theme, translateTitle, translateSubtitle, showActionIndicator, fontScale, alert
}) => ( }) => (
<View style={[styles.container, disabled && styles.disabled, { height: BASE_HEIGHT * fontScale }]} testID={testID}> <View style={[styles.container, disabled && styles.disabled, { height: BASE_HEIGHT * fontScale }]} testID={testID}>
{left {left
@ -61,7 +70,12 @@ const Content = React.memo(({
) )
: null} : null}
<View style={styles.textContainer}> <View style={styles.textContainer}>
<Text style={[styles.title, { color: color || themes[theme].titleText }]} numberOfLines={1}>{translateTitle ? I18n.t(title) : title}</Text> <View style={styles.textAlertContainer}>
<Text style={[styles.title, { color: color || themes[theme].titleText }]} numberOfLines={1}>{translateTitle ? I18n.t(title) : title}</Text>
{alert ? (
<CustomIcon style={[styles.alertIcon, { color: themes[theme].dangerColor }]} size={ICON_SIZE} name='info' />
) : null}
</View>
{subtitle {subtitle
? <Text style={[styles.subtitle, { color: themes[theme].auxiliaryText }]} numberOfLines={1}>{translateSubtitle ? I18n.t(subtitle) : subtitle}</Text> ? <Text style={[styles.subtitle, { color: themes[theme].auxiliaryText }]} numberOfLines={1}>{translateSubtitle ? I18n.t(subtitle) : subtitle}</Text>
: null : null
@ -123,7 +137,8 @@ Content.propTypes = {
translateTitle: PropTypes.bool, translateTitle: PropTypes.bool,
translateSubtitle: PropTypes.bool, translateSubtitle: PropTypes.bool,
showActionIndicator: PropTypes.bool, showActionIndicator: PropTypes.bool,
fontScale: PropTypes.number fontScale: PropTypes.number,
alert: PropTypes.bool
}; };
Content.defaultProps = { Content.defaultProps = {

View File

@ -1,2 +1,3 @@
export const PADDING_HORIZONTAL = 12; export const PADDING_HORIZONTAL = 12;
export const BASE_HEIGHT = 46; export const BASE_HEIGHT = 46;
export const ICON_SIZE = 20;

View File

@ -1,6 +1,6 @@
import React from 'react'; import React from 'react';
import { import {
View, StyleSheet, Text, Animated, Easing View, StyleSheet, Text, Animated, Easing, Linking
} from 'react-native'; } from 'react-native';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
@ -24,6 +24,9 @@ const SERVICE_HEIGHT = 58;
const BORDER_RADIUS = 2; const BORDER_RADIUS = 2;
const SERVICES_COLLAPSED_HEIGHT = 174; const SERVICES_COLLAPSED_HEIGHT = 174;
const LOGIN_STYPE_POPUP = 'popup';
const LOGIN_STYPE_REDIRECT = 'redirect';
const styles = StyleSheet.create({ const styles = StyleSheet.create({
serviceButton: { serviceButton: {
borderRadius: BORDER_RADIUS, borderRadius: BORDER_RADIUS,
@ -122,9 +125,9 @@ class LoginServices extends React.PureComponent {
const endpoint = 'https://accounts.google.com/o/oauth2/auth'; const endpoint = 'https://accounts.google.com/o/oauth2/auth';
const redirect_uri = `${ server }/_oauth/google?close`; const redirect_uri = `${ server }/_oauth/google?close`;
const scope = 'email'; const scope = 'email';
const state = this.getOAuthState(); const state = this.getOAuthState(LOGIN_STYPE_REDIRECT);
const params = `?client_id=${ clientId }&redirect_uri=${ redirect_uri }&scope=${ scope }&state=${ state }&response_type=code`; const params = `?client_id=${ clientId }&redirect_uri=${ redirect_uri }&scope=${ scope }&state=${ state }&response_type=code`;
this.openOAuth({ url: `${ endpoint }${ params }` }); Linking.openURL(`${ endpoint }${ params }`);
} }
onPressLinkedin = () => { onPressLinkedin = () => {
@ -219,9 +222,16 @@ class LoginServices extends React.PureComponent {
} }
} }
getOAuthState = () => { getOAuthState = (loginStyle = LOGIN_STYPE_POPUP) => {
const credentialToken = random(43); const credentialToken = random(43);
return Base64.encodeURI(JSON.stringify({ loginStyle: 'popup', credentialToken, isCordova: true })); let obj = { loginStyle, credentialToken, isCordova: true };
if (loginStyle === LOGIN_STYPE_REDIRECT) {
obj = {
...obj,
redirectUrl: 'rocketchat://auth'
};
}
return Base64.encodeURI(JSON.stringify(obj));
} }
openOAuth = ({ url, ssoToken, authType = 'oauth' }) => { openOAuth = ({ url, ssoToken, authType = 'oauth' }) => {

View File

@ -32,7 +32,7 @@ class RoomHeaderContainer extends Component {
shouldComponentUpdate(nextProps) { shouldComponentUpdate(nextProps) {
const { const {
type, title, subtitle, status, statusText, connecting, connected, onPress, usersTyping, width, height type, title, subtitle, status, statusText, connecting, connected, onPress, usersTyping, width, height, teamMain
} = this.props; } = this.props;
if (nextProps.type !== type) { if (nextProps.type !== type) {
return true; return true;
@ -67,6 +67,9 @@ class RoomHeaderContainer extends Component {
if (nextProps.onPress !== onPress) { if (nextProps.onPress !== onPress) {
return true; return true;
} }
if (nextProps.teamMain !== teamMain) {
return true;
}
return false; return false;
} }

View File

@ -30,6 +30,7 @@ const RoomTypeIcon = React.memo(({
return <Status style={[iconStyle, { color: STATUS_COLORS[status] ?? STATUS_COLORS.offline }]} size={size} status={status} />; return <Status style={[iconStyle, { color: STATUS_COLORS[status] ?? STATUS_COLORS.offline }]} size={size} status={status} />;
} }
// TODO: move this to a separate function
let icon = 'channel-private'; let icon = 'channel-private';
if (teamMain) { if (teamMain) {
icon = `teams${ type === 'p' ? '-private' : '' }`; icon = `teams${ type === 'p' ? '-private' : '' }`;

View File

@ -4,19 +4,18 @@ import { Text, Clipboard } from 'react-native';
import styles from './styles'; import styles from './styles';
import { themes } from '../../constants/colors'; import { themes } from '../../constants/colors';
import openLink from '../../utils/openLink';
import { LISTENER } from '../Toast'; import { LISTENER } from '../Toast';
import EventEmitter from '../../utils/events'; import EventEmitter from '../../utils/events';
import I18n from '../../i18n'; import I18n from '../../i18n';
const Link = React.memo(({ const Link = React.memo(({
children, link, theme children, link, theme, onLinkPress
}) => { }) => {
const handlePress = () => { const handlePress = () => {
if (!link) { if (!link || !onLinkPress) {
return; return;
} }
openLink(link, theme); onLinkPress(link);
}; };
const childLength = React.Children.toArray(children).filter(o => o).length; const childLength = React.Children.toArray(children).filter(o => o).length;
@ -40,7 +39,8 @@ const Link = React.memo(({
Link.propTypes = { Link.propTypes = {
children: PropTypes.node, children: PropTypes.node,
link: PropTypes.string, link: PropTypes.string,
theme: PropTypes.string theme: PropTypes.string,
onLinkPress: PropTypes.func
}; };
export default Link; export default Link;

View File

@ -82,7 +82,8 @@ class Markdown extends PureComponent {
preview: PropTypes.bool, preview: PropTypes.bool,
theme: PropTypes.string, theme: PropTypes.string,
testID: PropTypes.string, testID: PropTypes.string,
style: PropTypes.array style: PropTypes.array,
onLinkPress: PropTypes.func
}; };
constructor(props) { constructor(props) {
@ -218,11 +219,12 @@ class Markdown extends PureComponent {
}; };
renderLink = ({ children, href }) => { renderLink = ({ children, href }) => {
const { theme } = this.props; const { theme, onLinkPress } = this.props;
return ( return (
<MarkdownLink <MarkdownLink
link={href} link={href}
theme={theme} theme={theme}
onLinkPress={onLinkPress}
> >
{children} {children}
</MarkdownLink> </MarkdownLink>

View File

@ -45,7 +45,7 @@ const Content = React.memo((props) => {
} else if (props.isEncrypted) { } else if (props.isEncrypted) {
content = <Text style={[styles.textInfo, { color: themes[props.theme].auxiliaryText }]}>{I18n.t('Encrypted_message')}</Text>; content = <Text style={[styles.textInfo, { color: themes[props.theme].auxiliaryText }]}>{I18n.t('Encrypted_message')}</Text>;
} else { } else {
const { baseUrl, user } = useContext(MessageContext); const { baseUrl, user, onLinkPress } = useContext(MessageContext);
content = ( content = (
<Markdown <Markdown
msg={props.msg} msg={props.msg}
@ -61,6 +61,7 @@ const Content = React.memo((props) => {
tmid={props.tmid} tmid={props.tmid}
useRealName={props.useRealName} useRealName={props.useRealName}
theme={props.theme} theme={props.theme}
onLinkPress={onLinkPress}
/> />
); );
} }

View File

@ -19,6 +19,7 @@ import Discussion from './Discussion';
import Content from './Content'; import Content from './Content';
import ReadReceipt from './ReadReceipt'; import ReadReceipt from './ReadReceipt';
import CallButton from './CallButton'; import CallButton from './CallButton';
import { themes } from '../../constants/colors';
const MessageInner = React.memo((props) => { const MessageInner = React.memo((props) => {
if (props.type === 'discussion-created') { if (props.type === 'discussion-created') {
@ -120,6 +121,7 @@ const MessageTouchable = React.memo((props) => {
onLongPress={onLongPress} onLongPress={onLongPress}
onPress={onPress} onPress={onPress}
disabled={(props.isInfo && !props.isThreadReply) || props.archived || props.isTemp} disabled={(props.isInfo && !props.isThreadReply) || props.archived || props.isTemp}
style={{ backgroundColor: props.highlighted ? themes[props.theme].headerBackground : null }}
> >
<View> <View>
<Message {...props} /> <Message {...props} />
@ -134,7 +136,9 @@ MessageTouchable.propTypes = {
isInfo: PropTypes.bool, isInfo: PropTypes.bool,
isThreadReply: PropTypes.bool, isThreadReply: PropTypes.bool,
isTemp: PropTypes.bool, isTemp: PropTypes.bool,
archived: PropTypes.bool archived: PropTypes.bool,
highlighted: PropTypes.bool,
theme: PropTypes.string
}; };
Message.propTypes = { Message.propTypes = {

View File

@ -1,4 +1,4 @@
import React from 'react'; import React, { memo, useEffect, useState } from 'react';
import { View } from 'react-native'; import { View } from 'react-native';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
@ -8,24 +8,29 @@ import { themes } from '../../constants/colors';
import I18n from '../../i18n'; import I18n from '../../i18n';
import Markdown from '../markdown'; import Markdown from '../markdown';
const RepliedThread = React.memo(({ const RepliedThread = memo(({
tmid, tmsg, isHeader, fetchThreadName, id, isEncrypted, theme tmid, tmsg, isHeader, fetchThreadName, id, isEncrypted, theme
}) => { }) => {
if (!tmid || !isHeader) { if (!tmid || !isHeader) {
return null; return null;
} }
if (!tmsg) { const [msg, setMsg] = useState(isEncrypted ? I18n.t('Encrypted_message') : tmsg);
fetchThreadName(tmid, id); const fetch = async() => {
const threadName = await fetchThreadName(tmid, id);
setMsg(threadName);
};
useEffect(() => {
if (!msg) {
fetch();
}
}, []);
if (!msg) {
return null; return null;
} }
let msg = tmsg;
if (isEncrypted) {
msg = I18n.t('Encrypted_message');
}
return ( return (
<View style={styles.repliedThread} testID={`message-thread-replied-on-${ msg }`}> <View style={styles.repliedThread} testID={`message-thread-replied-on-${ msg }`}>
<CustomIcon name='threads' size={20} style={styles.repliedThreadIcon} color={themes[theme].tintColor} /> <CustomIcon name='threads' size={20} style={styles.repliedThreadIcon} color={themes[theme].tintColor} />
@ -45,23 +50,6 @@ const RepliedThread = React.memo(({
</View> </View>
</View> </View>
); );
}, (prevProps, nextProps) => {
if (prevProps.tmid !== nextProps.tmid) {
return false;
}
if (prevProps.tmsg !== nextProps.tmsg) {
return false;
}
if (prevProps.isEncrypted !== nextProps.isEncrypted) {
return false;
}
if (prevProps.isHeader !== nextProps.isHeader) {
return false;
}
if (prevProps.theme !== nextProps.theme) {
return false;
}
return true;
}); });
RepliedThread.propTypes = { RepliedThread.propTypes = {

View File

@ -142,10 +142,13 @@ const Reply = React.memo(({
if (!attachment) { if (!attachment) {
return null; return null;
} }
const { baseUrl, user } = useContext(MessageContext); const { baseUrl, user, jumpToMessage } = useContext(MessageContext);
const onPress = () => { const onPress = () => {
let url = attachment.title_link || attachment.author_link; let url = attachment.title_link || attachment.author_link;
if (attachment.message_link) {
return jumpToMessage(attachment.message_link);
}
if (!url) { if (!url) {
return; return;
} }

View File

@ -80,7 +80,7 @@ const UrlContent = React.memo(({ title, description, theme }) => (
}); });
const Url = React.memo(({ url, index, theme }) => { const Url = React.memo(({ url, index, theme }) => {
if (!url) { if (!url || url?.ignoreParse) {
return null; return null;
} }

View File

@ -9,6 +9,7 @@ import { SYSTEM_MESSAGES, getMessageTranslation } from './utils';
import { E2E_MESSAGE_TYPE, E2E_STATUS } from '../../lib/encryption/constants'; import { E2E_MESSAGE_TYPE, E2E_STATUS } from '../../lib/encryption/constants';
import messagesStatus from '../../constants/messagesStatus'; import messagesStatus from '../../constants/messagesStatus';
import { withTheme } from '../../theme'; import { withTheme } from '../../theme';
import openLink from '../../utils/openLink';
class MessageContainer extends React.Component { class MessageContainer extends React.Component {
static propTypes = { static propTypes = {
@ -33,6 +34,7 @@ class MessageContainer extends React.Component {
autoTranslateLanguage: PropTypes.string, autoTranslateLanguage: PropTypes.string,
status: PropTypes.number, status: PropTypes.number,
isIgnored: PropTypes.bool, isIgnored: PropTypes.bool,
highlighted: PropTypes.bool,
getCustomEmoji: PropTypes.func, getCustomEmoji: PropTypes.func,
onLongPress: PropTypes.func, onLongPress: PropTypes.func,
onReactionPress: PropTypes.func, onReactionPress: PropTypes.func,
@ -50,7 +52,9 @@ class MessageContainer extends React.Component {
blockAction: PropTypes.func, blockAction: PropTypes.func,
theme: PropTypes.string, theme: PropTypes.string,
threadBadgeColor: PropTypes.string, threadBadgeColor: PropTypes.string,
toggleFollowThread: PropTypes.func toggleFollowThread: PropTypes.func,
jumpToMessage: PropTypes.func,
onPress: PropTypes.func
} }
static defaultProps = { static defaultProps = {
@ -89,10 +93,15 @@ class MessageContainer extends React.Component {
shouldComponentUpdate(nextProps, nextState) { shouldComponentUpdate(nextProps, nextState) {
const { isManualUnignored } = this.state; const { isManualUnignored } = this.state;
const { theme, threadBadgeColor, isIgnored } = this.props; const {
theme, threadBadgeColor, isIgnored, highlighted
} = this.props;
if (nextProps.theme !== theme) { if (nextProps.theme !== theme) {
return true; return true;
} }
if (nextProps.highlighted !== highlighted) {
return true;
}
if (nextProps.threadBadgeColor !== threadBadgeColor) { if (nextProps.threadBadgeColor !== threadBadgeColor) {
return true; return true;
} }
@ -112,10 +121,15 @@ class MessageContainer extends React.Component {
} }
onPress = debounce(() => { onPress = debounce(() => {
const { onPress } = this.props;
if (this.isIgnored) { if (this.isIgnored) {
return this.onIgnoredMessagePress(); return this.onIgnoredMessagePress();
} }
if (onPress) {
return onPress();
}
const { item, isThreadRoom } = this.props; const { item, isThreadRoom } = this.props;
Keyboard.dismiss(); Keyboard.dismiss();
@ -265,12 +279,69 @@ class MessageContainer extends React.Component {
} }
} }
onLinkPress = (link) => {
const { item, theme, jumpToMessage } = this.props;
const isMessageLink = item?.attachments?.findIndex(att => att?.message_link === link) !== -1;
if (isMessageLink) {
return jumpToMessage(link);
}
openLink(link, theme);
}
render() { render() {
const { const {
item, user, style, archived, baseUrl, useRealName, broadcast, fetchThreadName, showAttachment, timeFormat, isReadReceiptEnabled, autoTranslateRoom, autoTranslateLanguage, navToRoomInfo, getCustomEmoji, isThreadRoom, callJitsi, blockAction, rid, theme, threadBadgeColor, toggleFollowThread item,
user,
style,
archived,
baseUrl,
useRealName,
broadcast,
fetchThreadName,
showAttachment,
timeFormat,
isReadReceiptEnabled,
autoTranslateRoom,
autoTranslateLanguage,
navToRoomInfo,
getCustomEmoji,
isThreadRoom,
callJitsi,
blockAction,
rid,
theme,
threadBadgeColor,
toggleFollowThread,
jumpToMessage,
highlighted
} = this.props; } = this.props;
const { const {
id, msg, ts, attachments, urls, reactions, t, avatar, emoji, u, alias, editedBy, role, drid, dcount, dlm, tmid, tcount, tlm, tmsg, mentions, channels, unread, blocks, autoTranslate: autoTranslateMessage, replies id,
msg,
ts,
attachments,
urls,
reactions,
t,
avatar,
emoji,
u,
alias,
editedBy,
role,
drid,
dcount,
dlm,
tmid,
tcount,
tlm,
tmsg,
mentions,
channels,
unread,
blocks,
autoTranslate: autoTranslateMessage,
replies
} = item; } = item;
let message = msg; let message = msg;
@ -294,6 +365,8 @@ class MessageContainer extends React.Component {
onEncryptedPress: this.onEncryptedPress, onEncryptedPress: this.onEncryptedPress,
onDiscussionPress: this.onDiscussionPress, onDiscussionPress: this.onDiscussionPress,
onReactionLongPress: this.onReactionLongPress, onReactionLongPress: this.onReactionLongPress,
onLinkPress: this.onLinkPress,
jumpToMessage,
threadBadgeColor, threadBadgeColor,
toggleFollowThread, toggleFollowThread,
replies replies
@ -347,6 +420,7 @@ class MessageContainer extends React.Component {
callJitsi={callJitsi} callJitsi={callJitsi}
blockAction={blockAction} blockAction={blockAction}
theme={theme} theme={theme}
highlighted={highlighted}
/> />
</MessageContext.Provider> </MessageContext.Provider>
); );

5
app/definition/ITeam.js Normal file
View File

@ -0,0 +1,5 @@
// https://github.com/RocketChat/Rocket.Chat/blob/develop/definition/ITeam.ts
export const TEAM_TYPE = {
PUBLIC: 0,
PRIVATE: 1
};

View File

@ -95,6 +95,7 @@ export const setLanguage = (l) => {
moment.locale(toMomentLocale(locale)); moment.locale(toMomentLocale(locale));
}; };
i18n.translations = { en: translations.en?.() };
const defaultLanguage = { languageTag: 'en', isRTL: false }; const defaultLanguage = { languageTag: 'en', isRTL: false };
const availableLanguages = Object.keys(translations); const availableLanguages = Object.keys(translations);
const { languageTag } = RNLocalize.findBestAvailableLanguage(availableLanguages) || defaultLanguage; const { languageTag } = RNLocalize.findBestAvailableLanguage(availableLanguages) || defaultLanguage;

View File

@ -33,7 +33,7 @@
"error-invalid-date": "التاريخ غير صالح", "error-invalid-date": "التاريخ غير صالح",
"error-invalid-description": "الوصف غير صالح", "error-invalid-description": "الوصف غير صالح",
"error-invalid-domain": "عنوان الموقع غير صالح", "error-invalid-domain": "عنوان الموقع غير صالح",
"error-invalid-email": "عنوان البريد اﻹلكتروني غير صالح {{emai}}", "error-invalid-email": "عنوان البريد اﻹلكتروني غير صالح {{email}}",
"error-invalid-email-address": "عنوان البريد اﻹلكتروني غير صالح", "error-invalid-email-address": "عنوان البريد اﻹلكتروني غير صالح",
"error-invalid-file-height": "ارتفاع الملف غير صالح", "error-invalid-file-height": "ارتفاع الملف غير صالح",
"error-invalid-file-type": "نوع الملف غير صالح", "error-invalid-file-type": "نوع الملف غير صالح",
@ -100,7 +100,6 @@
"announcement": "إعلان", "announcement": "إعلان",
"Announcement": "إعلان", "Announcement": "إعلان",
"Apply_Your_Certificate": "طبق شهادتك", "Apply_Your_Certificate": "طبق شهادتك",
"Applying_a_theme_will_change_how_the_app_looks": "سيؤدي تطبيق السمة إلى تغيير شكل التطبيق",
"ARCHIVE": "أرشفة", "ARCHIVE": "أرشفة",
"archive": "أرشفة", "archive": "أرشفة",
"are_typing": "يكتب", "are_typing": "يكتب",
@ -184,8 +183,6 @@
"deleting_room": "حذف الغرفة", "deleting_room": "حذف الغرفة",
"description": "وصف", "description": "وصف",
"Description": "وصف", "Description": "وصف",
"DESKTOP_OPTIONS": "خيارات سطح المكتب",
"DESKTOP_NOTIFICATIONS": "إشعارات سطح المكتب",
"Desktop_Alert_info": "هذه الإشعارات ترسل لسطح المكتب", "Desktop_Alert_info": "هذه الإشعارات ترسل لسطح المكتب",
"Directory": "مجلد", "Directory": "مجلد",
"Direct_Messages": "رسالة مباشرة", "Direct_Messages": "رسالة مباشرة",
@ -213,7 +210,6 @@
"Email_Notification_Mode_Disabled": "معطل", "Email_Notification_Mode_Disabled": "معطل",
"Email_or_password_field_is_empty": "حقل البريد الإلكتروني أو كلمة المرور فارغ", "Email_or_password_field_is_empty": "حقل البريد الإلكتروني أو كلمة المرور فارغ",
"Email": "البريد الإلكتروني", "Email": "البريد الإلكتروني",
"EMAIL": "البريد الإلكتروني",
"email": "البريد الإلكتروني", "email": "البريد الإلكتروني",
"Empty_title": "عنوان فارغ", "Empty_title": "عنوان فارغ",
"Enable_Auto_Translate": "تمكين الترجمة التلقائية", "Enable_Auto_Translate": "تمكين الترجمة التلقائية",
@ -270,7 +266,6 @@
"I_Saved_My_E2E_Password": "قمت بحفظ كلمة المرور الطرفية", "I_Saved_My_E2E_Password": "قمت بحفظ كلمة المرور الطرفية",
"IP": " عنوان بروتوكول الإنترنت (الآيبي)", "IP": " عنوان بروتوكول الإنترنت (الآيبي)",
"In_app": "في التطبيق", "In_app": "في التطبيق",
"IN_APP_AND_DESKTOP": "داخل التطبيق وسطح المكتب",
"In_App_and_Desktop_Alert_info": "يعرض شعاراً أعلى الشاشة عندما يكون التطبيق مفتوحًا، ويعرض إشعاراً على سطح المكتب", "In_App_and_Desktop_Alert_info": "يعرض شعاراً أعلى الشاشة عندما يكون التطبيق مفتوحًا، ويعرض إشعاراً على سطح المكتب",
"Invisible": "غير مرئي", "Invisible": "غير مرئي",
"Invite": "دعوة", "Invite": "دعوة",
@ -289,6 +284,7 @@
"last_message": "الرسالة الأخيرة", "last_message": "الرسالة الأخيرة",
"Leave_channel": "مغادرة القناة", "Leave_channel": "مغادرة القناة",
"leaving_room": "مغادرة الغرفة", "leaving_room": "مغادرة الغرفة",
"Leave": "مغادرة الغرفة",
"leave": "مغادرة", "leave": "مغادرة",
"Legal": "قانوني", "Legal": "قانوني",
"Light": "ساطع", "Light": "ساطع",
@ -398,7 +394,6 @@
"Profile": "الملف الشخصي", "Profile": "الملف الشخصي",
"Public_Channel": "قناة عامة", "Public_Channel": "قناة عامة",
"Public": "عام", "Public": "عام",
"PUSH_NOTIFICATIONS": "الإشعارات",
"Push_Notifications_Alert_Info": "يتم إرسال هذه الإشعارات إليك عندما لا يكون التطبيق مفتوحاً", "Push_Notifications_Alert_Info": "يتم إرسال هذه الإشعارات إليك عندما لا يكون التطبيق مفتوحاً",
"Quote": "اقتباس", "Quote": "اقتباس",
"Reactions_are_disabled": "التفاعل معطل", "Reactions_are_disabled": "التفاعل معطل",
@ -446,9 +441,9 @@
"Room_Members": "أعضاء الغرفة", "Room_Members": "أعضاء الغرفة",
"Room_name_changed": "تم تغيير اسم الغرفة إلى: {{name}} من قبل {{userBy}}", "Room_name_changed": "تم تغيير اسم الغرفة إلى: {{name}} من قبل {{userBy}}",
"SAVE": "حفظ", "SAVE": "حفظ",
"Saved": "تم الحفظ",
"Save_Changes": "حفظ التغيرات", "Save_Changes": "حفظ التغيرات",
"Save": "حفظ", "Save": "حفظ",
"Saved": "تم الحفظ",
"saving_preferences": "حفظ التفضيلات", "saving_preferences": "حفظ التفضيلات",
"saving_profile": "حفظ الملف الشخصي", "saving_profile": "حفظ الملف الشخصي",
"saving_settings": "حفظ الإعدادات", "saving_settings": "حفظ الإعدادات",
@ -657,5 +652,6 @@
"You_will_be_logged_out_from_other_locations": "سيتم تسجيل خروج من الأماكن الأخرى", "You_will_be_logged_out_from_other_locations": "سيتم تسجيل خروج من الأماكن الأخرى",
"Logged_out_of_other_clients_successfully": "تم تسجيل الخروج من الأماكن الأخرى بنجاح", "Logged_out_of_other_clients_successfully": "تم تسجيل الخروج من الأماكن الأخرى بنجاح",
"Logout_failed": "فشل تسجيل الخروج!", "Logout_failed": "فشل تسجيل الخروج!",
"Log_analytics_events": "تحليلات سجل الأحداث" "Log_analytics_events": "تحليلات سجل الأحداث",
"invalid-room": "غرفة غير صالحة"
} }

View File

@ -14,7 +14,7 @@
"error-delete-protected-role": "Eine geschützte Rolle kann nicht gelöscht werden", "error-delete-protected-role": "Eine geschützte Rolle kann nicht gelöscht werden",
"error-department-not-found": "Abteilung nicht gefunden", "error-department-not-found": "Abteilung nicht gefunden",
"error-direct-message-file-upload-not-allowed": "Dateifreigabe in direkten Nachrichten nicht zulässig", "error-direct-message-file-upload-not-allowed": "Dateifreigabe in direkten Nachrichten nicht zulässig",
"error-duplicate-channel-name": "Ein Kanal mit dem Namen {{channel_name}} ist bereits vorhanden", "error-duplicate-channel-name": "Ein Kanal mit dem Namen {{room_name}} ist bereits vorhanden",
"error-email-domain-blacklisted": "Die E-Mail-Domain wird auf die schwarze Liste gesetzt", "error-email-domain-blacklisted": "Die E-Mail-Domain wird auf die schwarze Liste gesetzt",
"error-email-send-failed": "Fehler beim Versuch, eine E-Mail zu senden: {{message}}", "error-email-send-failed": "Fehler beim Versuch, eine E-Mail zu senden: {{message}}",
"error-save-image": "Fehler beim Speichern des Bildes", "error-save-image": "Fehler beim Speichern des Bildes",
@ -33,7 +33,7 @@
"error-invalid-date": "Ungültiges Datum angegeben", "error-invalid-date": "Ungültiges Datum angegeben",
"error-invalid-description": "Ungültige Beschreibung", "error-invalid-description": "Ungültige Beschreibung",
"error-invalid-domain": "Ungültige Domain", "error-invalid-domain": "Ungültige Domain",
"error-invalid-email": "Ungültige E-Mail {{emai}}", "error-invalid-email": "Ungültige E-Mail {{email}}",
"error-invalid-email-address": "Ungültige E-Mail-Adresse", "error-invalid-email-address": "Ungültige E-Mail-Adresse",
"error-invalid-file-height": "Ungültige Dateihöhe", "error-invalid-file-height": "Ungültige Dateihöhe",
"error-invalid-file-type": "Ungültiger Dateityp", "error-invalid-file-type": "Ungültiger Dateityp",
@ -61,6 +61,7 @@
"error-message-editing-blocked": "Die Bearbeitung von Nachrichten ist gesperrt", "error-message-editing-blocked": "Die Bearbeitung von Nachrichten ist gesperrt",
"error-message-size-exceeded": "Die Nachrichtengröße überschreitet Message_MaxAllowedSize", "error-message-size-exceeded": "Die Nachrichtengröße überschreitet Message_MaxAllowedSize",
"error-missing-unsubscribe-link": "Du musst den Link [abbestellen] angeben.", "error-missing-unsubscribe-link": "Du musst den Link [abbestellen] angeben.",
"error-no-owner-channel": "Dieser Raum gehört dir nicht",
"error-no-tokens-for-this-user": "Für diesen Benutzer gibt es keine Token", "error-no-tokens-for-this-user": "Für diesen Benutzer gibt es keine Token",
"error-not-allowed": "Nicht erlaubt", "error-not-allowed": "Nicht erlaubt",
"error-not-authorized": "Nicht berechtigt", "error-not-authorized": "Nicht berechtigt",
@ -69,7 +70,7 @@
"error-role-in-use": "Rolle kann nicht gelöscht werden, da sie gerade verwendet wird", "error-role-in-use": "Rolle kann nicht gelöscht werden, da sie gerade verwendet wird",
"error-role-name-required": "Der Rollenname ist erforderlich", "error-role-name-required": "Der Rollenname ist erforderlich",
"error-the-field-is-required": "Das Feld {{field}} ist erforderlich.", "error-the-field-is-required": "Das Feld {{field}} ist erforderlich.",
"error-too-many-requests": "Fehler, zu viele Anfragen. Du musst {{Sekunden}} Sekunden warten, bevor du es erneut versuchst.", "error-too-many-requests": "Fehler, zu viele Anfragen. Du musst {{seconds}} Sekunden warten, bevor du es erneut versuchst.",
"error-user-is-not-activated": "Benutzer ist nicht aktiviert", "error-user-is-not-activated": "Benutzer ist nicht aktiviert",
"error-user-has-no-roles": "Benutzer hat keine Rollen", "error-user-has-no-roles": "Benutzer hat keine Rollen",
"error-user-limit-exceeded": "Die Anzahl der Benutzer, die du zu #channel_name einladen möchtest, überschreitet die vom Administrator festgelegte Grenze", "error-user-limit-exceeded": "Die Anzahl der Benutzer, die du zu #channel_name einladen möchtest, überschreitet die vom Administrator festgelegte Grenze",
@ -78,6 +79,7 @@
"error-user-registration-disabled": "Die Benutzerregistrierung ist deaktiviert", "error-user-registration-disabled": "Die Benutzerregistrierung ist deaktiviert",
"error-user-registration-secret": "Die Benutzerregistrierung ist nur über eine geheime URL möglich", "error-user-registration-secret": "Die Benutzerregistrierung ist nur über eine geheime URL möglich",
"error-you-are-last-owner": "Du bist der letzte Besitzer. Bitte setze einen neuen Besitzer, bevor du den Raum verlässt.", "error-you-are-last-owner": "Du bist der letzte Besitzer. Bitte setze einen neuen Besitzer, bevor du den Raum verlässt.",
"error-status-not-allowed": "Unsichtbar-Status ist deaktiviert",
"Actions": "Aktionen", "Actions": "Aktionen",
"activity": "Aktivität", "activity": "Aktivität",
"Activity": "Aktivität", "Activity": "Aktivität",
@ -90,6 +92,7 @@
"alert": "Benachrichtigung", "alert": "Benachrichtigung",
"alerts": "Benachrichtigungen", "alerts": "Benachrichtigungen",
"All_users_in_the_channel_can_write_new_messages": "Alle Benutzer im Kanal können neue Nachrichten schreiben", "All_users_in_the_channel_can_write_new_messages": "Alle Benutzer im Kanal können neue Nachrichten schreiben",
"All_users_in_the_team_can_write_new_messages": "Alle Mitglieder eines Teams können neue Nachrichten schreiben",
"A_meaningful_name_for_the_discussion_room": "Ein aussagekräftiger Name für den Diskussionsraum", "A_meaningful_name_for_the_discussion_room": "Ein aussagekräftiger Name für den Diskussionsraum",
"All": "alle", "All": "alle",
"All_Messages": "Alle Nachrichten", "All_Messages": "Alle Nachrichten",
@ -117,7 +120,7 @@
"Block_user": "Benutzer blockieren", "Block_user": "Benutzer blockieren",
"Browser": "Browser", "Browser": "Browser",
"Broadcast_channel_Description": "Nur autorisierte Benutzer können neue Nachrichten schreiben, die anderen Benutzer können jedoch antworten", "Broadcast_channel_Description": "Nur autorisierte Benutzer können neue Nachrichten schreiben, die anderen Benutzer können jedoch antworten",
"Broadcast_Channel": "Broadcastkanal", "Broadcast_Channel": "Broadcast-Kanal",
"Busy": "Beschäftigt", "Busy": "Beschäftigt",
"By_proceeding_you_are_agreeing": "Indem du fortfährst, stimmst du zu unserem", "By_proceeding_you_are_agreeing": "Indem du fortfährst, stimmst du zu unserem",
"Cancel_editing": "Bearbeitung abbrechen", "Cancel_editing": "Bearbeitung abbrechen",
@ -134,7 +137,7 @@
"Clear_cookies_desc": "Diese Aktion wird alle Login-Cookies löschen und erlaubt es dir, dich mit einem anderen Konto anzumelden.", "Clear_cookies_desc": "Diese Aktion wird alle Login-Cookies löschen und erlaubt es dir, dich mit einem anderen Konto anzumelden.",
"Clear_cookies_yes": "Ja, Cookies löschen", "Clear_cookies_yes": "Ja, Cookies löschen",
"Clear_cookies_no": "Nein, Cookies behalten", "Clear_cookies_no": "Nein, Cookies behalten",
"Click_to_join": "Klicken um teilzunehmen!", "Click_to_join": "Klicken um beizutreten!",
"Close": "Schließen", "Close": "Schließen",
"Close_emoji_selector": "Schließe die Emoji-Auswahl", "Close_emoji_selector": "Schließe die Emoji-Auswahl",
"Closing_chat": "Chat schließen", "Closing_chat": "Chat schließen",
@ -167,10 +170,10 @@
"Create_Channel": "Kanal erstellen", "Create_Channel": "Kanal erstellen",
"Create_Direct_Messages": "Direkt-Nachricht erstellen", "Create_Direct_Messages": "Direkt-Nachricht erstellen",
"Create_Discussion": "Diskussion erstellen", "Create_Discussion": "Diskussion erstellen",
"Created_snippet": "Erstellt ein Snippet", "Created_snippet": "ein Snippet erstellt",
"Create_a_new_workspace": "Erstelle einen neuen Arbeitsbereich", "Create_a_new_workspace": "Erstelle einen neuen Arbeitsbereich",
"Create": "Erstellen", "Create": "Erstellen",
"Custom_Status": "eigener Status", "Custom_Status": "Eigener Status",
"Dark": "Dunkel", "Dark": "Dunkel",
"Dark_level": "Dunkelstufe", "Dark_level": "Dunkelstufe",
"Default": "Standard", "Default": "Standard",
@ -180,13 +183,15 @@
"delete": "löschen", "delete": "löschen",
"Delete": "Löschen", "Delete": "Löschen",
"DELETE": "LÖSCHEN", "DELETE": "LÖSCHEN",
"move": "verschieben",
"deleting_room": "lösche Raum", "deleting_room": "lösche Raum",
"description": "Beschreibung", "description": "Beschreibung",
"Description": "Beschreibung", "Description": "Beschreibung",
"Desktop_Options": "Desktop-Einstellungen", "Desktop_Options": "Desktop-Einstellungen",
"Desktop_Notifications": "Desktop-Benachrichtigungen", "Desktop_Notifications": "Desktop-Benachrichtigungen",
"Desktop_Alert_info": "Diese Benachrichtigungen werden auf dem Desktop angezeigt",
"Directory": "Verzeichnis", "Directory": "Verzeichnis",
"Direct_Messages": "Direkte Nachrichten", "Direct_Messages": "Direktnachrichten",
"Disable_notifications": "Benachrichtigungen deaktiveren", "Disable_notifications": "Benachrichtigungen deaktiveren",
"Discussions": "Diskussionen", "Discussions": "Diskussionen",
"Discussion_Desc": "Hilft dir die Übersicht zu behalten! Durch das Erstellen einer Diskussion wird ein Unter-Kanal im ausgewählten Raum erzeugt und beide verknüpft.", "Discussion_Desc": "Hilft dir die Übersicht zu behalten! Durch das Erstellen einer Diskussion wird ein Unter-Kanal im ausgewählten Raum erzeugt und beide verknüpft.",
@ -207,7 +212,7 @@
"Edit_Status": "Status ändern", "Edit_Status": "Status ändern",
"Edit_Invite": "Einladung bearbeiten", "Edit_Invite": "Einladung bearbeiten",
"End_to_end_encrypted_room": "Ende-zu-Ende-verschlüsselter Raum", "End_to_end_encrypted_room": "Ende-zu-Ende-verschlüsselter Raum",
"end_to_end_encryption": "Nicht mehr Ende-zu-Ende-verschlüsseln", "end_to_end_encryption": "Nicht mehr Ende-zu-Ende verschlüsseln",
"Email_Notification_Mode_All": "Jede Erwähnung/Direktnachricht", "Email_Notification_Mode_All": "Jede Erwähnung/Direktnachricht",
"Email_Notification_Mode_Disabled": "Deaktiviert", "Email_Notification_Mode_Disabled": "Deaktiviert",
"Email_or_password_field_is_empty": "Das E-Mail- oder Passwortfeld ist leer", "Email_or_password_field_is_empty": "Das E-Mail- oder Passwortfeld ist leer",
@ -224,6 +229,7 @@
"Encryption_error_title": "Dein Verschlüsselungs-Passwort scheint falsch zu sein", "Encryption_error_title": "Dein Verschlüsselungs-Passwort scheint falsch zu sein",
"Encryption_error_desc": "Es war nicht möglich deinen Verschlüsselungs-Key zu importieren.", "Encryption_error_desc": "Es war nicht möglich deinen Verschlüsselungs-Key zu importieren.",
"Everyone_can_access_this_channel": "Jeder kann auf diesen Kanal zugreifen", "Everyone_can_access_this_channel": "Jeder kann auf diesen Kanal zugreifen",
"Everyone_can_access_this_team": "Jeder kann auf dieses Team zugreifen",
"Error_uploading": "Fehler beim Hochladen", "Error_uploading": "Fehler beim Hochladen",
"Expiration_Days": "läuft ab (Tage)", "Expiration_Days": "läuft ab (Tage)",
"Favorite": "Favorisieren", "Favorite": "Favorisieren",
@ -268,7 +274,7 @@
"I_Saved_My_E2E_Password": "Ich habe mein Ende-zu-Ende-Passwort gesichert", "I_Saved_My_E2E_Password": "Ich habe mein Ende-zu-Ende-Passwort gesichert",
"IP": "IP", "IP": "IP",
"In_app": "In-App-Browser", "In_app": "In-App-Browser",
"In_App_And_Desktop": "In-app und Desktop", "In_App_And_Desktop": "In-App und Desktop",
"In_App_and_Desktop_Alert_info": "Zeigt ein Banner oben am Bildschirm, wenn die App geöffnet ist und eine Benachrichtigung auf dem Desktop.", "In_App_and_Desktop_Alert_info": "Zeigt ein Banner oben am Bildschirm, wenn die App geöffnet ist und eine Benachrichtigung auf dem Desktop.",
"Invisible": "Unsichtbar", "Invisible": "Unsichtbar",
"Invite": "Einladen", "Invite": "Einladen",
@ -276,7 +282,7 @@
"is_not_a_valid_RocketChat_instance": "ist keine gültige Rocket.Chat-Instanz", "is_not_a_valid_RocketChat_instance": "ist keine gültige Rocket.Chat-Instanz",
"is_typing": "schreibt", "is_typing": "schreibt",
"Invalid_or_expired_invite_token": "Ungültiger oder abgelaufener Einladungscode", "Invalid_or_expired_invite_token": "Ungültiger oder abgelaufener Einladungscode",
"Invalid_server_version": "Der Server, zu dem du dich verbinden möchtest, verwendet eine Version, die von der App nicht mehr unterstützt wird: {{currentVersion}}.\n\nWir benötigen Version {{MinVersion}}.", "Invalid_server_version": "Der Server, zu dem du dich verbinden möchtest, verwendet eine Version, die von der App nicht mehr unterstützt wird: {{currentVersion}}.\n\nWir benötigen Version {{minVersion}}.",
"Invite_Link": "Einladungs-Link", "Invite_Link": "Einladungs-Link",
"Invite_users": "Benutzer einladen", "Invite_users": "Benutzer einladen",
"Join": "Beitreten", "Join": "Beitreten",
@ -285,16 +291,18 @@
"Join_our_open_workspace": "Tritt unserem offenen Arbeitsbereich bei", "Join_our_open_workspace": "Tritt unserem offenen Arbeitsbereich bei",
"Join_your_workspace": "Tritt deinem Arbeitsbereich bei", "Join_your_workspace": "Tritt deinem Arbeitsbereich bei",
"Just_invited_people_can_access_this_channel": "Nur eingeladene Personen können auf diesen Kanal zugreifen", "Just_invited_people_can_access_this_channel": "Nur eingeladene Personen können auf diesen Kanal zugreifen",
"Just_invited_people_can_access_this_team": "Nur eingeladene Personen können auf das Team zugreifen",
"Language": "Sprache", "Language": "Sprache",
"last_message": "letzte Nachricht", "last_message": "letzte Nachricht",
"Leave_channel": "Kanal verlassen", "Leave_channel": "Kanal verlassen",
"leaving_room": "Raum verlassen", "leaving_room": "Raum verlassen",
"Leave": "Raum verlassen",
"leave": "verlassen", "leave": "verlassen",
"Legal": "Rechtliches", "Legal": "Rechtliches",
"Light": "Hell", "Light": "Hell",
"License": "Lizenz", "License": "Lizenz",
"Livechat": "Live-Chat", "Livechat": "Live-Chat",
"Livechat_edit": "Livechat bearbeiten", "Livechat_edit": "Live-Chat bearbeiten",
"Login": "Anmeldung", "Login": "Anmeldung",
"Login_error": "Deine Zugangsdaten wurden abgelehnt! Bitte versuche es erneut.", "Login_error": "Deine Zugangsdaten wurden abgelehnt! Bitte versuche es erneut.",
"Login_with": "Einloggen mit", "Login_with": "Einloggen mit",
@ -325,6 +333,7 @@
"My_servers": "Meine Server", "My_servers": "Meine Server",
"N_people_reacted": "{{n}} Leute haben reagiert", "N_people_reacted": "{{n}} Leute haben reagiert",
"N_users": "{{n}} Benutzer", "N_users": "{{n}} Benutzer",
"N_channels": "{{n}} Kanäle",
"name": "Name", "name": "Name",
"Name": "Name", "Name": "Name",
"Navigation_history": "Navigations-Verlauf", "Navigation_history": "Navigations-Verlauf",
@ -400,7 +409,6 @@
"Public": "Öffentlich", "Public": "Öffentlich",
"Push_Notifications": "Push-Benachrichtigungen", "Push_Notifications": "Push-Benachrichtigungen",
"Push_Notifications_Alert_Info": "Diese Benachrichtigungen werden dir zugestellt, wenn die App nicht geöffnet ist.", "Push_Notifications_Alert_Info": "Diese Benachrichtigungen werden dir zugestellt, wenn die App nicht geöffnet ist.",
"Desktop_Alert_info": "Diese Benachrichtigungen werden auf dem Desktop angezeigt",
"Quote": "Zitat", "Quote": "Zitat",
"Reactions_are_disabled": "Reaktionen sind deaktiviert", "Reactions_are_disabled": "Reaktionen sind deaktiviert",
"Reactions_are_enabled": "Reaktionen sind aktiviert", "Reactions_are_enabled": "Reaktionen sind aktiviert",
@ -435,6 +443,7 @@
"Review_app_unable_store": "Kann {{store}} nicht öffnen", "Review_app_unable_store": "Kann {{store}} nicht öffnen",
"Review_this_app": "App bewerten", "Review_this_app": "App bewerten",
"Remove": "Entfernen", "Remove": "Entfernen",
"remove": "entfernen",
"Roles": "Rollen", "Roles": "Rollen",
"Room_actions": "Raumaktionen", "Room_actions": "Raumaktionen",
"Room_changed_announcement": "Raumansage geändert in: {{announcement}} von {{userBy}}", "Room_changed_announcement": "Raumansage geändert in: {{announcement}} von {{userBy}}",
@ -517,7 +526,7 @@
"Take_a_video": "Video aufnehmen", "Take_a_video": "Video aufnehmen",
"Take_it": "Annehmen!", "Take_it": "Annehmen!",
"tap_to_change_status": "Tippen um den Status zu ändern", "tap_to_change_status": "Tippen um den Status zu ändern",
"Tap_to_view_servers_list": "Hier tippen, um die Serverliste anzuzeigen", "Tap_to_view_servers_list": "Tippen, um die Serverliste anzuzeigen",
"Terms_of_Service": " Nutzungsbedingungen", "Terms_of_Service": " Nutzungsbedingungen",
"Theme": "Erscheinungsbild", "Theme": "Erscheinungsbild",
"The_user_wont_be_able_to_type_in_roomName": "Dem Nutzer wird es nicht möglich sein in {{roomName}} zu schreiben", "The_user_wont_be_able_to_type_in_roomName": "Dem Nutzer wird es nicht möglich sein in {{roomName}} zu schreiben",
@ -592,7 +601,7 @@
"You_can_search_using_RegExp_eg": "Du kannst mit RegExp suchen. z.B. `/ ^ text $ / i`", "You_can_search_using_RegExp_eg": "Du kannst mit RegExp suchen. z.B. `/ ^ text $ / i`",
"You_colon": "Du: ", "You_colon": "Du: ",
"you_were_mentioned": "Du wurdest erwähnt", "you_were_mentioned": "Du wurdest erwähnt",
"You_were_removed_from_channel": "Du wurdest aus dem Kanal {{channel}} entfernt", "You_were_removed_from_channel": "Du wurdest aus {{channel}} entfernt",
"you": "du", "you": "du",
"You": "Du", "You": "Du",
"Logged_out_by_server": "Du bist vom Server abgemeldet worden. Bitte melde dich wieder an.", "Logged_out_by_server": "Du bist vom Server abgemeldet worden. Bitte melde dich wieder an.",
@ -610,7 +619,7 @@
"You_will_unset_a_certificate_for_this_server": "Du entfernst ein Zertifikat für diesen Server", "You_will_unset_a_certificate_for_this_server": "Du entfernst ein Zertifikat für diesen Server",
"Change_Language": "Sprache ändern", "Change_Language": "Sprache ändern",
"Crash_report_disclaimer": "Wir verfolgen niemals den Inhalt deiner Chats. Der Crash-Report enthält nur für uns relevante Informationen um das Problem zu erkennen und zu beheben.", "Crash_report_disclaimer": "Wir verfolgen niemals den Inhalt deiner Chats. Der Crash-Report enthält nur für uns relevante Informationen um das Problem zu erkennen und zu beheben.",
"Type_message": "Type message", "Type_message": "Nachricht schreiben",
"Room_search": "Raum-Suche", "Room_search": "Raum-Suche",
"Room_selection": "Raum-Auswahl 1...9", "Room_selection": "Raum-Auswahl 1...9",
"Next_room": "Nächster Raum", "Next_room": "Nächster Raum",
@ -623,7 +632,7 @@
"Reply_in_Thread": "Im Thread antworten", "Reply_in_Thread": "Im Thread antworten",
"Server_selection": "Server-Auswahl", "Server_selection": "Server-Auswahl",
"Server_selection_numbers": "Server-Auswahl 1...9", "Server_selection_numbers": "Server-Auswahl 1...9",
"Add_server": "Server hinufügen", "Add_server": "Server hinzufügen",
"New_line": "Zeilenumbruch", "New_line": "Zeilenumbruch",
"You_will_be_logged_out_of_this_application": "Du wirst in dieser Anwendung vom Server abgemeldet.", "You_will_be_logged_out_of_this_application": "Du wirst in dieser Anwendung vom Server abgemeldet.",
"Clear": "Löschen", "Clear": "Löschen",
@ -681,12 +690,9 @@
"No_threads_following": "Du folgst keinen Threads", "No_threads_following": "Du folgst keinen Threads",
"No_threads_unread": "Es gibt keine ungelesenen Threads", "No_threads_unread": "Es gibt keine ungelesenen Threads",
"Messagebox_Send_to_channel": "an Kanal senden", "Messagebox_Send_to_channel": "an Kanal senden",
"Set_as_leader": "Zum Diskussionsleiter ernennen", "Leader": "Leiter",
"Set_as_moderator": "Zum Moderator ernennen", "Moderator": "Moderator",
"Set_as_owner": "Zum Besitzer machen", "Owner": "Eigentümer",
"Remove_as_leader": "Als Diskussionsleiter entfernen",
"Remove_as_moderator": "Moderatorenrechte entfernen",
"Remove_as_owner": "Als Eigentümer entfernen",
"Remove_from_room": "Aus dem Raum entfernen", "Remove_from_room": "Aus dem Raum entfernen",
"Ignore": "Ignorieren", "Ignore": "Ignorieren",
"Unignore": "Nicht mehr ignorieren", "Unignore": "Nicht mehr ignorieren",
@ -704,5 +710,56 @@
"Direct_message": "Direktnachricht", "Direct_message": "Direktnachricht",
"Message_Ignored": "Nachricht ignoriert. Antippen um sie zu zeigen.", "Message_Ignored": "Nachricht ignoriert. Antippen um sie zu zeigen.",
"Enter_workspace_URL": "Arbeitsbereich-URL", "Enter_workspace_URL": "Arbeitsbereich-URL",
"Workspace_URL_Example": "z.B. https://rocketchat.deine-firma.de" "Workspace_URL_Example": "z.B. https://rocketchat.deine-firma.de",
"This_room_encryption_has_been_enabled_by__username_": "Die Verschlüsselung dieses Raums wurde von {{username}} aktiviert",
"This_room_encryption_has_been_disabled_by__username_": "Die Verschlüsselung dieses Raums wurde von {{username}} deaktiviert",
"Teams": "Teams",
"No_team_channels_found": "Keine Kanäle gefunden",
"Team_not_found": "Team nicht gefunden",
"Create_Team": "Team erstellen",
"Team_Name": "Team-Name",
"Private_Team": "Privates Team",
"Read_Only_Team": "Nur-Lesen-Team",
"Broadcast_Team": "Broadcast-Team",
"creating_team": "Team erstellen",
"team-name-already-exists": "Ein Team mit diesem Namen existiert bereits",
"Add_Channel_to_Team": "Kanal zum Team hinzufügen",
"Create_New": "Neu erstellen",
"Add_Existing": "Vorhandenes hinzufügen",
"Add_Existing_Channel": "Vorhandenen Kanal hinzufügen",
"Remove_from_Team": "Aus Team entfernen",
"Auto-join": "Automatischer Beitritt",
"Remove_Team_Room_Warning": "Möchten du diesen Kanal aus dem Team entfernen? Der Kanal wird zurück in den Arbeitsbereich verschoben.",
"Confirmation": "Bestätigung",
"invalid-room": "Ungültiger Raum",
"You_are_leaving_the_team": "Du verlässt das Team '{{team}}'",
"Leave_Team": "Team verlassen",
"Select_Team": "Team auswählen",
"Select_Team_Channels": "Wähle die Kanäle des Teams aus, die du verlassen möchtest.",
"Cannot_leave": "Verlassen nicht möglich",
"Cannot_remove": "Kann nicht entfernt werden",
"Cannot_delete": "Kann nicht gelöscht werden",
"Last_owner_team_room": "Du bist der letzte Eigentümer des Kanals. Wenn du das Team verlässt, bleibt der Kanal innerhalb des Teams aber du verwaltest ihn von außen.",
"last-owner-can-not-be-removed": "Letzter Besitzer kann nicht entfernt werden",
"Remove_User_Teams": "Wähle die Kanäle aus, aus denen der Benutzer entfernt werden soll.",
"Delete_Team": "Team löschen",
"Select_channels_to_delete": "Dies kann nicht rückgängig gemacht werden. Wenn du ein Team löschst, werden alle Chat-Inhalte und und Einstellungen gelöscht.\n\nWähle die Kanäle, die du löschen möchtest. Diejenigen, die du behalten möchtest, werden in deinem Arbeitsbereich verfügbar sein. Beachte, das öffentliche Kanäle öffentlich bleiben und für jeden sichtbar sein werden.",
"You_are_deleting_the_team": "Du löschst dieses Team",
"Removing_user_from_this_team": "Du entfernst {{user}} aus diesem Team",
"Remove_User_Team_Channels": "Wähle die Kanäle aus, aus denen der Benutzer entfernt werden soll.",
"Remove_Member": "Mitglied entfernen",
"leaving_team": "Team verlassen",
"removing_team": "Aus dem Team entfernen",
"moving_channel_to_team": "Kanal zu Team verschieben",
"deleting_team": "Team löschen",
"member-does-not-exist": "Mitglied existiert nicht",
"Convert": "Konvertieren",
"Convert_to_Team": "Zu Team konvertieren",
"Convert_to_Team_Warning": "Dies kann nicht rückgängig gemacht werden. Sobald du einen Kanal in ein Team umgewandelt hast, kannst du ihn nicht mehr zurück in einen Kanal verwandeln.",
"Move_to_Team": "Zu Team hinzufügen",
"Move_Channel_Paragraph": "Das Verschieben eines Kanals innerhalb eines Teams bedeutet, dass dieser Kanal im Kontext des Teams hinzugefügt wird, jedoch haben alle Mitglieder des Kanals, die nicht Mitglied des jeweiligen Teams sind, weiterhin Zugriff auf diesen Kanal, werden aber nicht als Teammitglieder hinzugefügt \n\nDie gesamte Verwaltung des Kanals wird weiterhin von den Eigentümern dieses Kanals vorgenommen.\n\nTeammitglieder und sogar Teameigentümer, die nicht Mitglied dieses Kanals sind, können keinen Zugriff auf den Inhalt des Kanals haben \n\nBitte beachte, dass der Besitzer des Teams in der Lage ist, Mitglieder aus dem Kanal zu entfernen.",
"Move_to_Team_Warning": "Nachdem du die vorherigen Anleitungen zu diesem Verhalten gelesen hast, möchtest du diesen Kanal immer noch in das ausgewählte Team verschieben?",
"Load_More": "Mehr laden",
"Load_Newer": "Neuere laden",
"Load_Older": "Ältere laden"
} }

View File

@ -14,7 +14,7 @@
"error-delete-protected-role": "Cannot delete a protected role", "error-delete-protected-role": "Cannot delete a protected role",
"error-department-not-found": "Department not found", "error-department-not-found": "Department not found",
"error-direct-message-file-upload-not-allowed": "File sharing not allowed in direct messages", "error-direct-message-file-upload-not-allowed": "File sharing not allowed in direct messages",
"error-duplicate-channel-name": "A channel with name {{channel_name}} exists", "error-duplicate-channel-name": "A channel with name {{room_name}} exists",
"error-email-domain-blacklisted": "The email domain is blacklisted", "error-email-domain-blacklisted": "The email domain is blacklisted",
"error-email-send-failed": "Error trying to send email: {{message}}", "error-email-send-failed": "Error trying to send email: {{message}}",
"error-save-image": "Error while saving image", "error-save-image": "Error while saving image",
@ -33,7 +33,7 @@
"error-invalid-date": "Invalid date provided.", "error-invalid-date": "Invalid date provided.",
"error-invalid-description": "Invalid description", "error-invalid-description": "Invalid description",
"error-invalid-domain": "Invalid domain", "error-invalid-domain": "Invalid domain",
"error-invalid-email": "Invalid email {{emai}}", "error-invalid-email": "Invalid email {{email}}",
"error-invalid-email-address": "Invalid email address", "error-invalid-email-address": "Invalid email address",
"error-invalid-file-height": "Invalid file height", "error-invalid-file-height": "Invalid file height",
"error-invalid-file-type": "Invalid file type", "error-invalid-file-type": "Invalid file type",
@ -61,6 +61,7 @@
"error-message-editing-blocked": "Message editing is blocked", "error-message-editing-blocked": "Message editing is blocked",
"error-message-size-exceeded": "Message size exceeds Message_MaxAllowedSize", "error-message-size-exceeded": "Message size exceeds Message_MaxAllowedSize",
"error-missing-unsubscribe-link": "You must provide the [unsubscribe] link.", "error-missing-unsubscribe-link": "You must provide the [unsubscribe] link.",
"error-no-owner-channel": "You don't own the channel",
"error-no-tokens-for-this-user": "There are no tokens for this user", "error-no-tokens-for-this-user": "There are no tokens for this user",
"error-not-allowed": "Not allowed", "error-not-allowed": "Not allowed",
"error-not-authorized": "Not authorized", "error-not-authorized": "Not authorized",
@ -78,6 +79,7 @@
"error-user-registration-disabled": "User registration is disabled", "error-user-registration-disabled": "User registration is disabled",
"error-user-registration-secret": "User registration is only allowed via Secret URL", "error-user-registration-secret": "User registration is only allowed via Secret URL",
"error-you-are-last-owner": "You are the last owner. Please set new owner before leaving the room.", "error-you-are-last-owner": "You are the last owner. Please set new owner before leaving the room.",
"error-status-not-allowed": "Invisible status is disabled",
"Actions": "Actions", "Actions": "Actions",
"activity": "activity", "activity": "activity",
"Activity": "Activity", "Activity": "Activity",
@ -90,6 +92,7 @@
"alert": "alert", "alert": "alert",
"alerts": "alerts", "alerts": "alerts",
"All_users_in_the_channel_can_write_new_messages": "All users in the channel can write new messages", "All_users_in_the_channel_can_write_new_messages": "All users in the channel can write new messages",
"All_users_in_the_team_can_write_new_messages": "All users in the team can write new messages",
"A_meaningful_name_for_the_discussion_room": "A meaningful name for the discussion room", "A_meaningful_name_for_the_discussion_room": "A meaningful name for the discussion room",
"All": "All", "All": "All",
"All_Messages": "All Messages", "All_Messages": "All Messages",
@ -180,6 +183,7 @@
"delete": "delete", "delete": "delete",
"Delete": "Delete", "Delete": "Delete",
"DELETE": "DELETE", "DELETE": "DELETE",
"move": "move",
"deleting_room": "deleting room", "deleting_room": "deleting room",
"description": "description", "description": "description",
"Description": "Description", "Description": "Description",
@ -225,6 +229,7 @@
"Encryption_error_title": "Your encryption password seems wrong", "Encryption_error_title": "Your encryption password seems wrong",
"Encryption_error_desc": "It wasn't possible to decode your encryption key to be imported.", "Encryption_error_desc": "It wasn't possible to decode your encryption key to be imported.",
"Everyone_can_access_this_channel": "Everyone can access this channel", "Everyone_can_access_this_channel": "Everyone can access this channel",
"Everyone_can_access_this_team": "Everyone can access this team",
"Error_uploading": "Error uploading", "Error_uploading": "Error uploading",
"Expiration_Days": "Expiration (Days)", "Expiration_Days": "Expiration (Days)",
"Favorite": "Favorite", "Favorite": "Favorite",
@ -286,10 +291,12 @@
"Join_our_open_workspace": "Join our open workspace", "Join_our_open_workspace": "Join our open workspace",
"Join_your_workspace": "Join your workspace", "Join_your_workspace": "Join your workspace",
"Just_invited_people_can_access_this_channel": "Just invited people can access this channel", "Just_invited_people_can_access_this_channel": "Just invited people can access this channel",
"Just_invited_people_can_access_this_team": "Just invited people can access this team",
"Language": "Language", "Language": "Language",
"last_message": "last message", "last_message": "last message",
"Leave_channel": "Leave channel", "Leave_channel": "Leave channel",
"leaving_room": "leaving room", "leaving_room": "leaving room",
"Leave": "Leave",
"leave": "leave", "leave": "leave",
"Legal": "Legal", "Legal": "Legal",
"Light": "Light", "Light": "Light",
@ -326,6 +333,7 @@
"My_servers": "My servers", "My_servers": "My servers",
"N_people_reacted": "{{n}} people reacted", "N_people_reacted": "{{n}} people reacted",
"N_users": "{{n}} users", "N_users": "{{n}} users",
"N_channels": "{{n}} channels",
"name": "name", "name": "name",
"Name": "Name", "Name": "Name",
"Navigation_history": "Navigation history", "Navigation_history": "Navigation history",
@ -435,6 +443,7 @@
"Review_app_unable_store": "Unable to open {{store}}", "Review_app_unable_store": "Unable to open {{store}}",
"Review_this_app": "Review this app", "Review_this_app": "Review this app",
"Remove": "Remove", "Remove": "Remove",
"remove": "remove",
"Roles": "Roles", "Roles": "Roles",
"Room_actions": "Room actions", "Room_actions": "Room actions",
"Room_changed_announcement": "Room announcement changed to: {{announcement}} by {{userBy}}", "Room_changed_announcement": "Room announcement changed to: {{announcement}} by {{userBy}}",
@ -681,12 +690,9 @@
"No_threads_following": "You are not following any threads", "No_threads_following": "You are not following any threads",
"No_threads_unread": "There are no unread threads", "No_threads_unread": "There are no unread threads",
"Messagebox_Send_to_channel": "Send to channel", "Messagebox_Send_to_channel": "Send to channel",
"Set_as_leader": "Set as leader", "Leader": "Leader",
"Set_as_moderator": "Set as moderator", "Moderator": "Moderator",
"Set_as_owner": "Set as owner", "Owner": "Owner",
"Remove_as_leader": "Remove as leader",
"Remove_as_moderator": "Remove as moderator",
"Remove_as_owner": "Remove as owner",
"Remove_from_room": "Remove from room", "Remove_from_room": "Remove from room",
"Ignore": "Ignore", "Ignore": "Ignore",
"Unignore": "Unignore", "Unignore": "Unignore",
@ -709,5 +715,52 @@
"This_room_encryption_has_been_disabled_by__username_": "This room's encryption has been disabled by {{username}}", "This_room_encryption_has_been_disabled_by__username_": "This room's encryption has been disabled by {{username}}",
"Teams": "Teams", "Teams": "Teams",
"No_team_channels_found": "No channels found", "No_team_channels_found": "No channels found",
"Team_not_found": "Team not found" "Team_not_found": "Team not found",
"Create_Team": "Create Team",
"Team_Name": "Team Name",
"Private_Team": "Private Team",
"Read_Only_Team": "Read Only Team",
"Broadcast_Team": "Broadcast Team",
"creating_team": "creating team",
"team-name-already-exists": "A team with that name already exists",
"Add_Channel_to_Team": "Add Channel to Team",
"Left_The_Team_Successfully": "Left the team successfully",
"Create_New": "Create New",
"Add_Existing": "Add Existing",
"Add_Existing_Channel": "Add Existing Channel",
"Remove_from_Team": "Remove from Team",
"Auto-join": "Auto-join",
"Remove_Team_Room_Warning": "Woud you like to remove this channel from the team? The channel will be moved back to the workspace",
"Confirmation": "Confirmation",
"invalid-room": "Invalid room",
"You_are_leaving_the_team": "You are leaving the team '{{team}}'",
"Leave_Team": "Leave Team",
"Select_Team": "Select Team",
"Select_Team_Channels": "Select the Team's channels you would like to leave.",
"Cannot_leave": "Cannot leave",
"Cannot_remove": "Cannot remove",
"Cannot_delete": "Cannot delete",
"Last_owner_team_room": "You are the last owner of this channel. Once you leave the team, the channel will be kept inside the team but you will be managing it from outside.",
"last-owner-can-not-be-removed": "Last owner cannot be removed",
"Remove_User_Teams": "Select channels you want the user to be removed from.",
"Delete_Team": "Delete Team",
"Select_channels_to_delete": "This can't be undone. Once you delete a team, all chat content and configuration will be deleted. \n\nSelect the channels you would like to delete. The ones you decide to keep will be available on your workspace. Notice that public channels will still be public and visible to everyone.",
"You_are_deleting_the_team": "You are deleting this team.",
"Removing_user_from_this_team": "You are removing {{user}} from this team",
"Remove_User_Team_Channels": "Select the channels you want the user to be removed from.",
"Remove_Member": "Remove Member",
"leaving_team": "leaving team",
"removing_team": "removing from team",
"moving_channel_to_team": "moving channel to team",
"deleting_team": "deleting team",
"member-does-not-exist": "Member does not exist",
"Convert": "Convert",
"Convert_to_Team": "Convert to Team",
"Convert_to_Team_Warning": "This can't be undone. Once you convert a channel to a team, you can not turn it back to a channel.",
"Move_to_Team": "Move to Team",
"Move_Channel_Paragraph": "Moving a channel inside a team means that this channel will be added in the teams context, however, all channels members, which are not members of the respective team, will still have access to this channel, but will not be added as teams members. \n\nAll channels management will still be made by the owners of this channel.\n\nTeams members and even teams owners, if not a member of this channel, can not have access to the channels content. \n\nPlease notice that the Teams owner will be able remove members from the Channel.",
"Move_to_Team_Warning": "After reading the previous intructions about this behavior, do you still want to move this channel to the selected team?",
"Load_More": "Load More",
"Load_Newer": "Load Newer",
"Load_Older": "Load Older"
} }

View File

@ -13,7 +13,7 @@
"error-delete-protected-role": "No se puede eliminar un rol protegido", "error-delete-protected-role": "No se puede eliminar un rol protegido",
"error-department-not-found": "Departamento no encontrado", "error-department-not-found": "Departamento no encontrado",
"error-direct-message-file-upload-not-allowed": "No se permite compartir archivos en mensajes directos", "error-direct-message-file-upload-not-allowed": "No se permite compartir archivos en mensajes directos",
"error-duplicate-channel-name": "Ya existe un canal con nombre {{channel_name}}", "error-duplicate-channel-name": "Ya existe un canal con nombre {{room_name}}",
"error-email-domain-blacklisted": "El dominio del correo electrónico está en la lista negra", "error-email-domain-blacklisted": "El dominio del correo electrónico está en la lista negra",
"error-email-send-failed": "Error al enviar el correo electrónico: {{message}}", "error-email-send-failed": "Error al enviar el correo electrónico: {{message}}",
"error-field-unavailable": "{{field}} ya está en uso :(", "error-field-unavailable": "{{field}} ya está en uso :(",
@ -25,12 +25,12 @@
"error-invalid-asset": "El archivo archivo no es correcto", "error-invalid-asset": "El archivo archivo no es correcto",
"error-invalid-channel": "El canal no es correcto.", "error-invalid-channel": "El canal no es correcto.",
"error-invalid-channel-start-with-chars": "Canal incorrecto. Debe comenzar con @ o #", "error-invalid-channel-start-with-chars": "Canal incorrecto. Debe comenzar con @ o #",
"error-invalid-custom-field": "Invalid custom field", "error-invalid-custom-field": "Campo personalizado no válido",
"error-invalid-custom-field-name": "Nombre inválido para el campo personalizado. Utilice sólo letras, números, guiones o guión bajo", "error-invalid-custom-field-name": "Nombre no válido para el campo personalizado. Utilice sólo letras, números, guiones o guión bajo",
"error-invalid-date": "La fecha proporcionada no es correcta.", "error-invalid-date": "La fecha proporcionada no es correcta.",
"error-invalid-description": "La descipción no es correcta", "error-invalid-description": "La descripción no es correcta",
"error-invalid-domain": "El dominio no es correcto", "error-invalid-domain": "El dominio no es correcto",
"error-invalid-email": "El email {{emai}} no es correcto", "error-invalid-email": "El email {{email}} no es correcto",
"error-invalid-email-address": "La dirección de correo no es correcta", "error-invalid-email-address": "La dirección de correo no es correcta",
"error-invalid-file-height": "La altura de la imagen no es correcta", "error-invalid-file-height": "La altura de la imagen no es correcta",
"error-invalid-file-type": "El formato del archivo no es correcto", "error-invalid-file-type": "El formato del archivo no es correcto",
@ -44,10 +44,10 @@
"error-invalid-redirectUri": "La URL de redirección no es correcta.", "error-invalid-redirectUri": "La URL de redirección no es correcta.",
"error-invalid-role": "El rol no es correcto", "error-invalid-role": "El rol no es correcto",
"error-invalid-room": "La sala no es correcta", "error-invalid-room": "La sala no es correcta",
"error-invalid-room-name": "No se puede asignar el nombre {{name}} a una sala.", "error-invalid-room-name": "No se puede asignar el nombre {{room_name}} a una sala.",
"error-invalid-room-type": "No se puede asginar el tipo {{type}} a una sala.", "error-invalid-room-type": "No se puede asignar el tipo {{type}} a una sala.",
"error-invalid-settings": "La configuración proporcionada no es correcta", "error-invalid-settings": "La configuración proporcionada no es correcta",
"error-invalid-subscription": "La subscripción no es correcta", "error-invalid-subscription": "La suscripción no es correcta",
"error-invalid-token": "El token no es correcto", "error-invalid-token": "El token no es correcto",
"error-invalid-triggerWords": "El triggerWords no es correcto", "error-invalid-triggerWords": "El triggerWords no es correcto",
"error-invalid-urls": "Las URLs no son correctas", "error-invalid-urls": "Las URLs no son correctas",
@ -62,25 +62,24 @@
"error-not-allowed": "No permitido", "error-not-allowed": "No permitido",
"error-not-authorized": "No autorizado", "error-not-authorized": "No autorizado",
"error-push-disabled": "El Push está desactivado", "error-push-disabled": "El Push está desactivado",
"error-remove-last-owner": "El usuario el único propietario existente. Debes establecer un nuevo propietario antes de eliminarlo.", "error-remove-last-owner": "El usuario es el único propietario existente. Debes establecer un nuevo propietario antes de eliminarlo.",
"error-role-in-use": "No puedes eliminar el rol dado que está en uso", "error-role-in-use": "No puedes eliminar el rol dado que está en uso",
"error-role-name-required": "Debes indicar el nombre del rol", "error-role-name-required": "Debes indicar el nombre del rol",
"error-the-field-is-required": "El campo {{field}} es obligatorio.", "error-the-field-is-required": "El campo {{field}} es obligatorio.",
"error-too-many-requests": "Hemos recibido demasiadas peticiones. Debes esperar {{seconds}} segundos antes de continuar. Por favor, sé paciente.", "error-too-many-requests": "Error, demasiadas peticiones. Debes esperar {{seconds}} segundos antes de continuar. Por favor, sé paciente.",
"error-user-is-not-activated": "El usuario no está activo", "error-user-is-not-activated": "El usuario no está activo",
"error-user-has-no-roles": "El usuario no tiene roles", "error-user-has-no-roles": "El usuario no tiene roles",
"error-user-limit-exceeded": "El número de usuarios que quieres invitiar al canal #channel_name supera el límite establecido por el adminitrador.", "error-user-limit-exceeded": "El número de usuarios que quieres invitar al canal #channel_name supera el límite establecido por el administrador.",
"error-user-not-in-room": "El usuario no está en la sala", "error-user-not-in-room": "El usuario no está en la sala",
"error-user-registration-custom-field": "error-user-registration-custom-field", "error-user-registration-custom-field": "error-user-registration-custom-field",
"error-user-registration-disabled": "El registro de usuario está deshabilitador", "error-user-registration-disabled": "El registro de usuario está deshabilitado",
"error-user-registration-secret": "El registro de usuarios sólo está permitido por URL secretas", "error-user-registration-secret": "El registro de usuarios sólo está permitido por URL secretas",
"error-you-are-last-owner": "El usuario el único propietario existente. Debes establecer un nuevo propietario antes de abandonar la sala.", "error-you-are-last-owner": "Eres el único propietario existente. Debes establecer un nuevo propietario antes de abandonar la sala.",
"Actions": "Acciones", "Actions": "Acciones",
"activity": "actividad", "activity": "actividad",
"Activity": "Actividad", "Activity": "Actividad",
"Add_Reaction": "Reaccionar", "Add_Reaction": "Añadir reacción",
"Add_Server": "Añadir servidor", "Add_Server": "Añadir servidor",
"Add_user": "Añadir usuario",
"Admin_Panel": "Panel de Control", "Admin_Panel": "Panel de Control",
"Alert": "Alerta", "Alert": "Alerta",
"alert": "alerta", "alert": "alerta",
@ -90,27 +89,27 @@
"All_Messages": "Todos los mensajes", "All_Messages": "Todos los mensajes",
"Allow_Reactions": "Permitir reacciones", "Allow_Reactions": "Permitir reacciones",
"Alphabetical": "Alfabético", "Alphabetical": "Alfabético",
"and_more": "más", "and_more": "y más",
"and": "y", "and": "y",
"announcement": "anuncio", "announcement": "anuncio",
"Announcement": "Anuncio", "Announcement": "Anuncio",
"Apply_Your_Certificate": "Applica tu Certificación", "Apply_Your_Certificate": "Aplica tu certificado",
"ARCHIVE": "FICHERO", "ARCHIVE": "FICHERO",
"archive": "Fichero", "archive": "fichero",
"are_typing": "escribiendo", "are_typing": "están escribiendo",
"Are_you_sure_question_mark": "¿Estás seguro?", "Are_you_sure_question_mark": "¿Estás seguro?",
"Are_you_sure_you_want_to_leave_the_room": "¿Deseas salir de la sala {{room}}?", "Are_you_sure_you_want_to_leave_the_room": "¿Deseas salir de la sala {{room}}?",
"Audio": "Audio", "Audio": "Audio",
"Authenticating": "Autenticando", "Authenticating": "Autenticando",
"Automatic": "Automático", "Automatic": "Automático",
"Auto_Translate": "Auto-Translate", "Auto_Translate": "Traducción automática",
"Avatar_changed_successfully": "Has cambiado tu Avatar!", "Avatar_changed_successfully": "¡Avatar modificado correctamente!",
"Avatar_Url": "URL del Avatar", "Avatar_Url": "URL del Avatar",
"Away": "Ausente", "Away": "Ausente",
"Back": "Volver", "Back": "Volver",
"Black": "Black", "Black": "Negro",
"Block_user": "Bloquear usuario", "Block_user": "Bloquear usuario",
"Broadcast_channel_Description": "Sólo los usuario permitidos pueden escribir nuevos mensajes, el resto podrán responder sobre los mismos.", "Broadcast_channel_Description": "Sólo los usuarios autorizados pueden escribir nuevos mensajes, el resto podrán responder sobre los mismos.",
"Broadcast_Channel": "Canal de Transmisión", "Broadcast_Channel": "Canal de Transmisión",
"Busy": "Ocupado", "Busy": "Ocupado",
"By_proceeding_you_are_agreeing": "Al proceder estarás de acuerdo", "By_proceeding_you_are_agreeing": "Al proceder estarás de acuerdo",
@ -122,35 +121,35 @@
"Channel_Name": "Nombre sala", "Channel_Name": "Nombre sala",
"Channels": "Salas", "Channels": "Salas",
"Chats": "Chats", "Chats": "Chats",
"Call_already_ended": "La llamada ya ha finalizado!", "Call_already_ended": "¡!La llamada ya ha finalizado!",
"Click_to_join": "Unirme!", "Click_to_join": "¡Unirme!",
"Close": "Cerrar", "Close": "Cerrar",
"Close_emoji_selector": "Cerrar selector de emojis", "Close_emoji_selector": "Cerrar selector de emojis",
"Choose": "Seleccionar", "Choose": "Seleccionar",
"Choose_from_library": "Seleccionar desde Galería", "Choose_from_library": "Seleccionar desde galería",
"Choose_file": "Seleccionar Archivo", "Choose_file": "Seleccionar archivo",
"Code": "Código", "Code": "Código",
"Collaborative": "Colaborativo", "Collaborative": "Colaborativo",
"Confirm": "Confirmar", "Confirm": "Confirmar",
"Connect": "Conectar", "Connect": "Conectar",
"Connected": "Conectado", "Connected": "Conectado",
"connecting_server": "conectando a servidor", "connecting_server": "conectando al servidor",
"Connecting": "Conectando...", "Connecting": "Conectando...",
"Contact_us": "Contactar", "Contact_us": "Contacta con nosotros",
"Contact_your_server_admin": "Contacta con el administrador.", "Contact_your_server_admin": "Contacta con el administrador.",
"Continue_with": "Continuar con", "Continue_with": "Continuar con",
"Copied_to_clipboard": "Copiado al portapapeles!", "Copied_to_clipboard": "¡Copiado al portapapeles!",
"Copy": "Copiar", "Copy": "Copiar",
"Permalink": "Enlace permanente", "Permalink": "Enlace permanente",
"Certificate_password": "Contraseña del certificado", "Certificate_password": "Contraseña del certificado",
"Whats_the_password_for_your_certificate": "¿Cuál es la contraseña de tu cerficiado?", "Whats_the_password_for_your_certificate": "¿Cuál es la contraseña de tu certificado?",
"Create_account": "Crear una cuenta", "Create_account": "Crear una cuenta",
"Create_Channel": "Crear Sala", "Create_Channel": "Crear sala",
"Created_snippet": "crear snippet", "Created_snippet": "crear mensaje en bloque",
"Create_a_new_workspace": "Crear un Workspace", "Create_a_new_workspace": "Crear un nuevo espacio de trabajo",
"Create": "Crear", "Create": "Crear",
"Dark": "Óscuro", "Dark": "Oscuro",
"Dark_level": "Nivel", "Dark_level": "Nivel de oscuridad",
"Default": "Por defecto", "Default": "Por defecto",
"Delete_Room_Warning": "Eliminar a un usuario causará la eliminación de todos los mensajes creados por dicho usuario. Esta operación no se puede deshacer.", "Delete_Room_Warning": "Eliminar a un usuario causará la eliminación de todos los mensajes creados por dicho usuario. Esta operación no se puede deshacer.",
"delete": "eliminar", "delete": "eliminar",
@ -159,9 +158,9 @@
"deleting_room": "eliminando sala", "deleting_room": "eliminando sala",
"description": "descripción", "description": "descripción",
"Description": "Descripción", "Description": "Descripción",
"Desktop_Options": "Opciones De Escritorio", "Desktop_Options": "Opciones de escritorio",
"Directory": "Directorio", "Directory": "Directorio",
"Direct_Messages": "Mensajes directo", "Direct_Messages": "Mensajes directos",
"Disable_notifications": "Desactivar notificaciones", "Disable_notifications": "Desactivar notificaciones",
"Discussions": "Conversaciones", "Discussions": "Conversaciones",
"Dont_Have_An_Account": "¿Todavía no tienes una cuenta?", "Dont_Have_An_Account": "¿Todavía no tienes una cuenta?",
@ -170,7 +169,7 @@
"edit": "editar", "edit": "editar",
"edited": "editado", "edited": "editado",
"Edit": "Editar", "Edit": "Editar",
"Email_or_password_field_is_empty": "El email o la contraseña están vacios", "Email_or_password_field_is_empty": "El email o la contraseña están vacíos",
"Email": "E-mail", "Email": "E-mail",
"email": "e-mail", "email": "e-mail",
"Enable_Auto_Translate": "Permitir Auto-Translate", "Enable_Auto_Translate": "Permitir Auto-Translate",
@ -185,9 +184,9 @@
"Finish_recording": "Finalizar grabación", "Finish_recording": "Finalizar grabación",
"Following_thread": "Siguiendo hilo", "Following_thread": "Siguiendo hilo",
"For_your_security_you_must_enter_your_current_password_to_continue": "Por seguridad, debes introducir tu contraseña para continuar", "For_your_security_you_must_enter_your_current_password_to_continue": "Por seguridad, debes introducir tu contraseña para continuar",
"Forgot_password_If_this_email_is_registered": "Si este email está registrado, te enviaremos las instrucciones para resetear tu contraseña.Si no recibes un email en un rato, vuelve aquí e inténtalo de nuevo.", "Forgot_password_If_this_email_is_registered": "Si este email está registrado, te enviaremos las instrucciones para resetear tu contraseña. Si no recibes un email en breve, vuelve aquí e inténtalo de nuevo.",
"Forgot_password": "Restablecer mi contraseña", "Forgot_password": "¿Ha olvidado su contraseña?",
"Forgot_Password": "Restabler mi Contraseña", "Forgot_Password": "Olvidé la contraseña",
"Full_table": "Click para ver la tabla completa", "Full_table": "Click para ver la tabla completa",
"Group_by_favorites": "Agrupar por favoritos", "Group_by_favorites": "Agrupar por favoritos",
"Group_by_type": "Agrupar por tipo", "Group_by_type": "Agrupar por tipo",
@ -195,29 +194,29 @@
"Has_joined_the_channel": "se ha unido al canal", "Has_joined_the_channel": "se ha unido al canal",
"Has_joined_the_conversation": "se ha unido a la conversación", "Has_joined_the_conversation": "se ha unido a la conversación",
"Has_left_the_channel": "ha dejado el canal", "Has_left_the_channel": "ha dejado el canal",
"In_App_And_Desktop": "In-app and Desktop", "In_App_And_Desktop": "En la aplicación y en el escritorio",
"In_App_and_Desktop_Alert_info": "Muestra un banner en la parte superior de la pantalla cuando la aplicación está abierta y muestra una notificación en el escritorio", "In_App_and_Desktop_Alert_info": "Muestra un banner en la parte superior de la pantalla cuando la aplicación esté abierta y muestra una notificación en el escritorio",
"Invisible": "Invisible", "Invisible": "Invisible",
"Invite": "Invitar", "Invite": "Invitar",
"is_a_valid_RocketChat_instance": "es una instancia válida Rocket.Chat", "is_a_valid_RocketChat_instance": "es una instancia válida de Rocket.Chat",
"is_not_a_valid_RocketChat_instance": "no es una instancia válida Rocket.Chat", "is_not_a_valid_RocketChat_instance": "no es una instancia válida de Rocket.Chat",
"is_typing": "escribiendo", "is_typing": "escribiendo",
"Invalid_server_version": "El servidor que intentas conectar está usando una versión que ya no es soportada por la aplicación : {{currentVersion}}. Requerimos una versión {{minVersion}}.", "Invalid_server_version": "El servidor que intentas conectar está usando una versión que ya no es soportada por la aplicación : {{currentVersion}}. Se requiere una versión {{minVersion}}.",
"Join": "Conectar", "Join": "Conectar",
"Just_invited_people_can_access_this_channel": "Sólo gente invitada puede acceder a este canal.", "Just_invited_people_can_access_this_channel": "Sólo gente invitada puede acceder a este canal.",
"Language": "Idioma", "Language": "Idioma",
"last_message": "último mensaje", "last_message": "último mensaje",
"Leave_channel": "Abandonar canal", "Leave_channel": "Abandonar el canal",
"leaving_room": "abandonando sala", "leaving_room": "abandonando sala",
"leave": "abandonar", "leave": "abandonar",
"Legal": "Legal", "Legal": "Legal",
"Light": "Claro", "Light": "Claro",
"License": "Licencia", "License": "Licencia",
"Livechat": "Livechat", "Livechat": "LiveChat",
"Login": "Acceder", "Login": "Inicio de sesión",
"Login_error": "¡Sus credenciales fueron rechazadas! Por favor, inténtelo de nuevo.", "Login_error": "¡Sus credenciales fueron rechazadas! Por favor, inténtelo de nuevo.",
"Login_with": "Acceder con", "Login_with": "Iniciar sesión con",
"Logout": "Salir", "Logout": "Cerrar sesión",
"members": "miembros", "members": "miembros",
"Members": "Miembros", "Members": "Miembros",
"Mentioned_Messages": "Mensajes mencionados", "Mentioned_Messages": "Mensajes mencionados",
@ -249,19 +248,19 @@
"No_pinned_messages": "No hay mensajes fijados", "No_pinned_messages": "No hay mensajes fijados",
"No_results_found": "No hay resultados", "No_results_found": "No hay resultados",
"No_starred_messages": "No hay mensajes destacados", "No_starred_messages": "No hay mensajes destacados",
"No_thread_messages": "No hay hilots", "No_thread_messages": "No hay hilos",
"No_Message": "Sin mensajes", "No_Message": "Sin mensajes",
"No_messages_yet": "No hay todavía mensajes", "No_messages_yet": "No hay mensajes todavía",
"No_Reactions": "No hay reacciones", "No_Reactions": "No hay reacciones",
"No_Read_Receipts": "No hay confirmaciones de lectura", "No_Read_Receipts": "No hay confirmaciones de lectura",
"Not_logged": "No logueado", "Not_logged": "No ha iniciado sesión",
"Not_RC_Server": "Esto no es un servidor de Rocket.Chat.\n{{contact}}", "Not_RC_Server": "Esto no es un servidor de Rocket.Chat.\n{{contact}}",
"Nothing": "Nada", "Nothing": "Nada",
"Nothing_to_save": "No hay nada para guardar!", "Nothing_to_save": "¡No hay nada por guardar!",
"Notify_active_in_this_room": "Notificar usuarios activos en esta sala", "Notify_active_in_this_room": "Notificar a los usuarios activos en esta sala",
"Notify_all_in_this_room": "Notificar a todos en esta sala", "Notify_all_in_this_room": "Notificar a todos en esta sala",
"Notifications": "Notificaciones", "Notifications": "Notificaciones",
"Notification_Duration": "Duración notificación", "Notification_Duration": "Duración de la notificación",
"Notification_Preferences": "Configuración de notificaciones", "Notification_Preferences": "Configuración de notificaciones",
"Offline": "Sin conexión", "Offline": "Sin conexión",
"Oops": "Oops!", "Oops": "Oops!",
@ -271,28 +270,28 @@
"Open_emoji_selector": "Abrir selector de emojis", "Open_emoji_selector": "Abrir selector de emojis",
"Open_Source_Communication": "Comunicación Open Source", "Open_Source_Communication": "Comunicación Open Source",
"Password": "Contraseña", "Password": "Contraseña",
"Permalink_copied_to_clipboard": "Enlace permanente copiado al portapapeles!", "Permalink_copied_to_clipboard": "¡Enlace permanente copiado al portapapeles!",
"Pin": "Fijar", "Pin": "Fijar",
"Pinned_Messages": "Mensajes fijados", "Pinned_Messages": "Mensajes fijados",
"pinned": "fijado", "pinned": "fijado",
"Pinned": "Fijado", "Pinned": "Fijado",
"Please_enter_your_password": "Por favor introduce tu contraseña", "Please_enter_your_password": "Por favor introduce la contraseña",
"Preferences": "Configuración", "Preferences": "Preferencias",
"Preferences_saved": "Configuración guardada!", "Preferences_saved": "¡Preferencias guardadas!",
"Privacy_Policy": "Política de Privacidad", "Privacy_Policy": "Política de privacidad",
"Private_Channel": "Canal privado", "Private_Channel": "Canal privado",
"Private_Groups": "Grupos privados", "Private_Groups": "Grupos privados",
"Private": "Privado", "Private": "Privado",
"Processing": "Procesando...", "Processing": "Procesando...",
"Profile_saved_successfully": "Perfil guardado correctamente!", "Profile_saved_successfully": "¡Perfil guardado correctamente!",
"Profile": "Perfil", "Profile": "Perfil",
"Public_Channel": "Canal público", "Public_Channel": "Canal público",
"Public": "Público", "Public": "Público",
"Push_Notifications": "Push Notifications", "Push_Notifications": "Notificaciones Push",
"Push_Notifications_Alert_Info": "Estas notificaciones se le entregan cuando la aplicación no está abierta", "Push_Notifications_Alert_Info": "Estas notificaciones se le entregan cuando la aplicación no está abierta",
"Quote": "Citar", "Quote": "Citar",
"Reactions_are_disabled": "Las reacciones están desactivadas", "Reactions_are_disabled": "Las reacciones están desactivadas",
"Reactions_are_enabled": "Las reacciones están habilitadas", "Reactions_are_enabled": "Las reacciones están activadas",
"Reactions": "Reacciones", "Reactions": "Reacciones",
"Read": "Leer", "Read": "Leer",
"Read_Only_Channel": "Canal de sólo lectura", "Read_Only_Channel": "Canal de sólo lectura",
@ -324,12 +323,12 @@
"Room_Info": "Información de la sala", "Room_Info": "Información de la sala",
"Room_Members": "Miembros de la sala", "Room_Members": "Miembros de la sala",
"Room_name_changed": "El nombre de la sala cambió a: {{name}} por {{userBy}}", "Room_name_changed": "El nombre de la sala cambió a: {{name}} por {{userBy}}",
"SAVE": "SAVE", "SAVE": "GUARDAR",
"Save_Changes": "Guardar cambios", "Save_Changes": "Guardar cambios",
"Save": "Guardar", "Save": "Guardar",
"saving_preferences": "guardando preferencias", "saving_preferences": "guardando preferencias",
"saving_profile": "guardando perfil", "saving_profile": "guardando perfil",
"saving_settings": "guardando confiración", "saving_settings": "guardando configuración",
"Search_Messages": "Buscar mensajes", "Search_Messages": "Buscar mensajes",
"Search": "Buscar", "Search": "Buscar",
"Search_by": "Buscar por", "Search_by": "Buscar por",
@ -350,14 +349,14 @@
"Server_version": "Versión servidor: {{version}}", "Server_version": "Versión servidor: {{version}}",
"Set_username_subtitle": "El nombre de usuario se utiliza para permitir que otros le mencionen en los mensajes", "Set_username_subtitle": "El nombre de usuario se utiliza para permitir que otros le mencionen en los mensajes",
"Settings": "Configuración", "Settings": "Configuración",
"Settings_succesfully_changed": "Configuración cambiada correctamente!", "Settings_succesfully_changed": "¡Configuración cambiada correctamente!",
"Share": "Compartir", "Share": "Compartir",
"Share_this_app": "Compartir esta App", "Share_this_app": "Compartir esta aplicación",
"Show_Unread_Counter": "Mostrar contador No leídos", "Show_Unread_Counter": "Mostrar contador de no leídos",
"Show_Unread_Counter_Info": "El contador de no leídos se muestra como una insignia a la derecha del canal, en la lista", "Show_Unread_Counter_Info": "El contador de no leídos se muestra como una insignia a la derecha del canal, en la lista",
"Sign_in_your_server": "Accede a tu servidor", "Sign_in_your_server": "Accede a tu servidor",
"Sign_Up": "Acceder", "Sign_Up": "Registrarse",
"Some_field_is_invalid_or_empty": "Algún campo es incorrecto o vacío", "Some_field_is_invalid_or_empty": "Algún campo no es correcto o está vacío",
"Sorting_by": "Ordenado por {{key}}", "Sorting_by": "Ordenado por {{key}}",
"Sound": "Sonido", "Sound": "Sonido",
"Star_room": "Destacar sala", "Star_room": "Destacar sala",
@ -365,18 +364,18 @@
"Starred_Messages": "Mensajes destacados", "Starred_Messages": "Mensajes destacados",
"starred": "destacado", "starred": "destacado",
"Starred": "Destacado", "Starred": "Destacado",
"Start_of_conversation": "Comiezo de la conversación", "Start_of_conversation": "Comienzo de la conversación",
"Started_discussion": "Comenzar una conversación:", "Started_discussion": "Comenzar una conversación:",
"Started_call": "Llamada iniciada por {{userBy}}", "Started_call": "Llamada iniciada por {{userBy}}",
"Submit": "Enviar", "Submit": "Enviar",
"Table": "Tabla", "Table": "Tabla",
"Take_a_photo": "Enviar Foto", "Take_a_photo": "Enviar una foto",
"Take_a_video": "Enviar Vídeo", "Take_a_video": "Enviar un vídeo",
"tap_to_change_status": "pulsa para cambiar el estado", "tap_to_change_status": "pulsa para cambiar el estado",
"Tap_to_view_servers_list": "Pulsa para ver la lista de servidores", "Tap_to_view_servers_list": "Pulsa para ver la lista de servidores",
"Terms_of_Service": "Términos de servicio", "Terms_of_Service": "Términos de servicio",
"Theme": "Tema", "Theme": "Tema",
"There_was_an_error_while_action": "Ha habido un error mientras {{action}}!", "There_was_an_error_while_action": "¡Ha habido un error mientras {{action}}!",
"This_room_is_blocked": "La sala está bloqueada", "This_room_is_blocked": "La sala está bloqueada",
"This_room_is_read_only": "Esta sala es de sólo lectura", "This_room_is_read_only": "Esta sala es de sólo lectura",
"Thread": "Hilo", "Thread": "Hilo",
@ -389,21 +388,21 @@
"Try_again": "Intentar de nuevo", "Try_again": "Intentar de nuevo",
"Two_Factor_Authentication": "Autenticación de doble factor", "Two_Factor_Authentication": "Autenticación de doble factor",
"Type_the_channel_name_here": "Escribe el nombre del canal aquí", "Type_the_channel_name_here": "Escribe el nombre del canal aquí",
"unarchive": "reactivar", "unarchive": "desarchivar",
"UNARCHIVE": "UNARCHIVE", "UNARCHIVE": "DESARCHIVAR",
"Unblock_user": "Desbloquear usuario", "Unblock_user": "Desbloquear usuario",
"Unfavorite": "Quitar Favorito", "Unfavorite": "Quitar favorito",
"Unfollowed_thread": "Dejar de seguir el Hilo", "Unfollowed_thread": "Dejar de seguir el hilo",
"Unmute": "Desmutear", "Unmute": "Desmutear",
"unmuted": "Desmuteado", "unmuted": "Desmuteado",
"Unpin": "Quitar estado Fijado", "Unpin": "Quitar estado fijado",
"unread_messages": "marcar como No leído", "unread_messages": "marcar como no leído",
"Unread": "Marcar como No leído", "Unread": "Marcar como no leído",
"Unread_on_top": "Mensajes No leídos en la parte superior", "Unread_on_top": "Mensajes no leídos en la parte superior",
"Unstar": "Quitar Destacado", "Unstar": "Quitar destacado",
"Updating": "Actualizando...", "Updating": "Actualizando...",
"Uploading": "Subiendo", "Uploading": "Subiendo",
"Upload_file_question_mark": "Subir fichero?", "Upload_file_question_mark": "¿Subir fichero?",
"Users": "Usuarios", "Users": "Usuarios",
"User_added_by": "Usuario {{userAdded}} añadido por {{userBy}}", "User_added_by": "Usuario {{userAdded}} añadido por {{userBy}}",
"User_has_been_key": "El usuario ha sido {{key}}", "User_has_been_key": "El usuario ha sido {{key}}",
@ -424,9 +423,9 @@
"Welcome": "Bienvenido", "Welcome": "Bienvenido",
"Whats_your_2fa": "¿Cuál es tu código 2FA?", "Whats_your_2fa": "¿Cuál es tu código 2FA?",
"Without_Servers": "Sin servidores", "Without_Servers": "Sin servidores",
"Yes_action_it": "Sí, {{action}}!", "Yes_action_it": "Sí, ¡{{action}}!",
"Yesterday": "Ayer", "Yesterday": "Ayer",
"You_are_in_preview_mode": "Estás en modo Vista Previa", "You_are_in_preview_mode": "Estás en modo vista previa",
"You_are_offline": "Estás desconectado", "You_are_offline": "Estás desconectado",
"You_can_search_using_RegExp_eg": "Puedes usar expresiones regulares. Por ejemplo, `/^text$/i`", "You_can_search_using_RegExp_eg": "Puedes usar expresiones regulares. Por ejemplo, `/^text$/i`",
"You_colon": "Tú: ", "You_colon": "Tú: ",
@ -436,7 +435,7 @@
"You_need_to_access_at_least_one_RocketChat_server_to_share_something": "Necesita acceder al menos a un servidor Rocket.Chat para compartir algo.", "You_need_to_access_at_least_one_RocketChat_server_to_share_something": "Necesita acceder al menos a un servidor Rocket.Chat para compartir algo.",
"Your_certificate": "Tu certificado", "Your_certificate": "Tu certificado",
"Version_no": "Versión: {{version}}", "Version_no": "Versión: {{version}}",
"You_will_not_be_able_to_recover_this_message": "No podrás recuperar este mensaje!", "You_will_not_be_able_to_recover_this_message": "¡No podrás recuperar este mensaje!",
"Change_Language": "Cambiar idioma", "Change_Language": "Cambiar idioma",
"Crash_report_disclaimer": "Nunca rastreamos el contenido de sus conversaciones. El informe del error sólo contiene información relevante para nosotros con el fin de identificar los problemas y solucionarlos.", "Crash_report_disclaimer": "Nunca rastreamos el contenido de sus conversaciones. El informe del error sólo contiene información relevante para nosotros con el fin de identificar los problemas y solucionarlos.",
"Type_message": "Escribir mensaje", "Type_message": "Escribir mensaje",

View File

@ -3,45 +3,45 @@
"1_user": "1 utilisateur", "1_user": "1 utilisateur",
"error-action-not-allowed": "{{action}} n'est pas autorisé", "error-action-not-allowed": "{{action}} n'est pas autorisé",
"error-application-not-found": "Application non trouvée", "error-application-not-found": "Application non trouvée",
"error-archived-duplicate-name": "Il y a un canal archivé avec nom {{room_name}}", "error-archived-duplicate-name": "Il y a un canal archivé avec le nom {{room_name}}",
"error-avatar-invalid-url": "URL d'avatar invalide: {{url}}", "error-avatar-invalid-url": "URL d'avatar invalide : {{url}}",
"error-avatar-url-handling": "Erreur lors de la gestion du paramètre d'avatar à partir d'une URL ({{url}}) pour {{username}}", "error-avatar-url-handling": "Erreur lors de la gestion du paramètre d'avatar à partir d'une URL ({{url}}) pour {{username}}",
"error-cant-invite-for-direct-room": "Impossible d'inviter l'utilisateur aux salles direct", "error-cant-invite-for-direct-room": "Impossible d'inviter l'utilisateur aux salons directs",
"error-could-not-change-email": "Impossible de changer l'adresse e-mail", "error-could-not-change-email": "Impossible de changer l'adresse e-mail",
"error-could-not-change-name": "Impossible de changer le nom", "error-could-not-change-name": "Impossible de changer le nom",
"error-could-not-change-username": "Impossible de changer le nom d'utilisateur", "error-could-not-change-username": "Impossible de changer le nom d'utilisateur",
"error-could-not-change-status": "Impossible de changer le statut", "error-could-not-change-status": "Impossible de changer le statut",
"error-delete-protected-role": "Impossible de supprimer un rôle protégé", "error-delete-protected-role": "Impossible de supprimer un rôle protégé",
"error-department-not-found": "Département introuvable", "error-department-not-found": "Département introuvable",
"error-direct-message-file-upload-not-allowed": "Le partage de fichiers n'est pas autorisé dans les messages directs", "error-direct-message-file-upload-not-allowed": "Partage de fichiers non autorisé dans les messages privés",
"error-duplicate-channel-name": "un canal avec nom {{channel_name}} existe", "error-duplicate-channel-name": "Un canal avec nom {{room_name}} existe",
"error-email-domain-blacklisted": "Le domaine de messagerie est sur liste noire", "error-email-domain-blacklisted": "Le domaine de messagerie est sur liste noire",
"error-email-send-failed": "Erreur lors de la tentative d'envoi d'un courrier électronique: {{message}}", "error-email-send-failed": "Erreur lors de la tentative d'envoi de l'e-mail : {{message}}",
"error-save-image": "Erreur en sauvegardant l'image", "error-save-image": "Erreur lors de l'enregistrement de l'image",
"error-save-video": "Erreur en sauvegardant la video", "error-save-video": "Erreur en sauvegardant la vidéo",
"error-field-unavailable": "{{field}} est déjà utilisé: (", "error-field-unavailable": "{{field}} est déjà utilisé: (",
"error-file-too-large": "Le fichier est trop volumineux", "error-file-too-large": "Le fichier est trop grand",
"error-importer-not-defined": "L'importateur n'a pas été défini correctement, il manque la classe import.", "error-importer-not-defined": "L'importateur n'a pas été défini correctement, il manque la classe Import.",
"error-input-is-not-a-valid-field": "{{input}} N'est pas valide {{field}}", "error-input-is-not-a-valid-field": "{{input}} n'est pas un {{field}} valide",
"error-invalid-actionlink": "Lien d'action invalide", "error-invalid-actionlink": "Lien d'action non valide",
"error-invalid-arguments": "Invalid arguments", "error-invalid-arguments": "Arguments non valides",
"error-invalid-asset": "élément incorrect", "error-invalid-asset": "Elément non valide",
"error-invalid-channel": "Canal invalide.", "error-invalid-channel": "Canal invalide.",
"error-invalid-channel-start-with-chars": "Canal invalide. Commence par @ ou #", "error-invalid-channel-start-with-chars": "Canal non valide. Commencez par @ ou #",
"error-invalid-custom-field": "Champ personnalisé incorrect", "error-invalid-custom-field": "Champ personnalisé non valide",
"error-invalid-custom-field-name": "Nom de champ personnalisé non valide. Utilisez uniquement des lettres, des chiffres, des traits d'union et de soulignement.", "error-invalid-custom-field-name": "Nom de champ personnalisé non valide. Utilisez uniquement des lettres, des chiffres, des traits d'union et des traits de soulignement.",
"error-invalid-date": "Date fournie invalide.", "error-invalid-date": "Date fournie non valide.",
"error-invalid-description": "Description invalide", "error-invalid-description": "Description invalide",
"error-invalid-domain": "Domaine invalide", "error-invalid-domain": "Domaine invalide",
"error-invalid-email": "Adresse e-mail non valide {{emai}}", "error-invalid-email": "E-mail {{email}} invalide",
"error-invalid-email-address": "Adresse e-mail invalide", "error-invalid-email-address": "Adresse e-mail invalide",
"error-invalid-file-height": "Hauteur de fichier non valide", "error-invalid-file-height": "Hauteur de fichier non valide",
"error-invalid-file-type": "Type de fichier invalide", "error-invalid-file-type": "Type de fichier invalide",
"error-invalid-file-width": "Largeur de fichier invalide", "error-invalid-file-width": "Largeur de fichier non valide",
"error-invalid-from-address": "Vous avez informé une adresse FROM invalide.", "error-invalid-from-address": "Vous avez renseigné une adresse FROM invalide.",
"error-invalid-integration": "Intégration invalide", "error-invalid-integration": "Intégration invalide",
"error-invalid-message": "Message invalide", "error-invalid-message": "Message invalide",
"error-invalid-method": "Méthode invalide", "error-invalid-method": "Méthode non valide",
"error-invalid-name": "Nom incorrect", "error-invalid-name": "Nom incorrect",
"error-invalid-password": "Mot de passe incorrect", "error-invalid-password": "Mot de passe incorrect",
"error-invalid-redirectUri": "RedirectUri invalide", "error-invalid-redirectUri": "RedirectUri invalide",
@ -50,47 +50,50 @@
"error-invalid-room-name": "{{room_name}} n'est pas un nom de salon valide", "error-invalid-room-name": "{{room_name}} n'est pas un nom de salon valide",
"error-invalid-room-type": "{{type}} n'est pas un type de salon valide.", "error-invalid-room-type": "{{type}} n'est pas un type de salon valide.",
"error-invalid-settings": "Paramètres fournis non valides", "error-invalid-settings": "Paramètres fournis non valides",
"error-invalid-subscription": "Subscription invalide", "error-invalid-subscription": "Abonnement invalide",
"error-invalid-token": "Jeton invalide", "error-invalid-token": "Jeton invalide",
"error-invalid-triggerWords": "Mots déclencheurs invalides", "error-invalid-triggerWords": "Mots déclencheurs invalides",
"error-invalid-urls": "URL non valides", "error-invalid-urls": "URL non valides",
"error-invalid-user": "Utilisateur invalide", "error-invalid-user": "Utilisateur invalide",
"error-invalid-username": "Nom d'utilisateur invalide", "error-invalid-username": "Nom d'utilisateur invalide",
"error-invalid-webhook-response": "L'URL webhook a répondu avec un statut autre que 200", "error-invalid-webhook-response": "L'URL du webhook a répondu avec un statut autre que 200",
"error-message-deleting-blocked": "La suppression du message est bloquée", "error-message-deleting-blocked": "La suppression du message est bloquée",
"error-message-editing-blocked": "La modification du message est bloquée", "error-message-editing-blocked": "La modification du message est bloquée",
"error-message-size-exceeded": "La taille du message dépasse Message_MaxAllowedSize", "error-message-size-exceeded": "La taille du message dépasse Message_MaxAllowedSize",
"error-missing-unsubscribe-link": "Vous devez fournir le [unsubscribe] lien.", "error-missing-unsubscribe-link": "Vous devez fournir le lien [unsubscribe].",
"error-no-owner-channel": "Vous n'êtes pas propriétaire du canal",
"error-no-tokens-for-this-user": "Il n'y a pas de jetons pour cet utilisateur", "error-no-tokens-for-this-user": "Il n'y a pas de jetons pour cet utilisateur",
"error-not-allowed": "Non autorisé", "error-not-allowed": "Interdit",
"error-not-authorized": "Non autorisé", "error-not-authorized": "Pas autorisé",
"error-push-disabled": "Push est désactivé", "error-push-disabled": "Push est désactivé",
"error-remove-last-owner": "Ceci est le dernier propriétaire. Veuillez définir un nouveau propriétaire avant de supprimer celui-ci.", "error-remove-last-owner": "C'est le dernier propriétaire. Veuillez définir un nouveau propriétaire avant de supprimer celui-ci.",
"error-role-in-use": "Impossible de supprimer le rôle car est utilisé", "error-role-in-use": "Impossible de supprimer le rôle car il est en cours d'utilisation",
"error-role-name-required": "Le nom du rôle est requis", "error-role-name-required": "Le nom du rôle est requis",
"error-the-field-is-required": "Le champ {{field}} est requis.", "error-the-field-is-required": "Le champ {{field}} est requis.",
"error-too-many-requests": "Erreur, trop de demandes. Ralentissez, s'il vous plaît. Vous devez attendre {{seconds}} secondes avant d'essayer à nouveau.", "error-too-many-requests": "Erreur, trop de demandes. Ralentissez, s'il vous plaît. Vous devez attendre {{seconds}} secondes avant de réessayer.",
"error-user-is-not-activated": "L'utilisateur n'est pas activé", "error-user-is-not-activated": "L'utilisateur n'est pas activé",
"error-user-has-no-roles": "L'utilisateur ne dispose pas d'un rôle", "error-user-has-no-roles": "L'utilisateur n'a aucun rôle",
"error-user-limit-exceeded": "Le nombre d'utilisateurs que vous essayez d'inviter à #channel_name dépasse la limite définie par l'administrateur", "error-user-limit-exceeded": "Le nombre d'utilisateurs que vous essayez d'inviter à #channel_name dépasse la limite définie par l'administrateur",
"error-user-not-in-room": "L'utilisateur n'est pas dans cette salle", "error-user-not-in-room": "L'utilisateur n'est pas dans ce salon",
"error-user-registration-custom-field": "error-user-registration-custom-field", "error-user-registration-custom-field": "error-user-registration-custom-field",
"error-user-registration-disabled": "L'enregistrement de l'utilisateur est désactivé", "error-user-registration-disabled": "L'enregistrement de l'utilisateur est désactivé",
"error-user-registration-secret": "Enregistrement de l'utilisateur est autorisée uniquement via l'URL secret", "error-user-registration-secret": "L'enregistrement de l'utilisateur n'est autorisé que via l'URL secrète",
"error-you-are-last-owner": "Vous êtes le dernier propriétaire. Veuillez définir un nouveau propriétaire avant de quitter la salle.", "error-you-are-last-owner": "Vous êtes le dernier propriétaire. Veuillez définir un nouveau propriétaire avant de quitter le salon.",
"error-status-not-allowed": "Le statut invisible est désactivé",
"Actions": "Actions", "Actions": "Actions",
"activity": "activité", "activity": "activité",
"Activity": "Activité", "Activity": "Activité",
"Add_Reaction": "Ajouter une réaction", "Add_Reaction": "Ajouter une réaction",
"Add_Server": "Ajouter un serveur", "Add_Server": "Ajouter un serveur",
"Add_users": "Ajouter des utilisateurs", "Add_users": "Ajouter des utilisateurs",
"Admin_Panel": "Panneau d'Administration", "Admin_Panel": "Panneau d'administration",
"Agent": "Agent", "Agent": "Agent",
"Alert": "Alerte", "Alert": "Alerte",
"alert": "alerte", "alert": "alerte",
"alerts": "alertes", "alerts": "alertes",
"All_users_in_the_channel_can_write_new_messages": "Tous les utilisateurs du canal peuvent écrire de nouveaux messages", "All_users_in_the_channel_can_write_new_messages": "Tous les utilisateurs du canal peuvent écrire de nouveaux messages",
"A_meaningful_name_for_the_discussion_room": "Un nom explicite pour la salle de discussion", "All_users_in_the_team_can_write_new_messages": "Tous les utilisateurs de l'équipe peuvent écrire de nouveaux messages",
"A_meaningful_name_for_the_discussion_room": "Un nom significatif pour le salon de discussion",
"All": "Tout", "All": "Tout",
"All_Messages": "Tous les messages", "All_Messages": "Tous les messages",
"Allow_Reactions": "Autoriser les réactions", "Allow_Reactions": "Autoriser les réactions",
@ -99,113 +102,137 @@
"and": "et", "and": "et",
"announcement": "annonce", "announcement": "annonce",
"Announcement": "Annonce", "Announcement": "Annonce",
"Apply_Your_Certificate": "Valider le Certificat", "Apply_Your_Certificate": "Appliquer votre certificat",
"ARCHIVE": "ARCHIVER", "ARCHIVE": "ARCHIVER",
"archive": "archiver", "archive": "archiver",
"are_typing": "sont en train d'écrire", "are_typing": "sont en train d'écrire",
"Are_you_sure_question_mark": "Êtes-vous sûr ?", "Are_you_sure_question_mark": "Êtes-vous sûr ?",
"Are_you_sure_you_want_to_leave_the_room": "Êtes-vous sûr de vouloir quitter le salon {{room}}?", "Are_you_sure_you_want_to_leave_the_room": "Êtes-vous sûr de vouloir quitter le salon {{room}} ?",
"Audio": "Audio", "Audio": "Audio",
"Authenticating": "Authentifier", "Authenticating": "Authentification",
"Automatic": "Automatique", "Automatic": "Automatique",
"Auto_Translate": "Traduction-Auto", "Auto_Translate": "Traduction automatique",
"Avatar_changed_successfully": "Avatar changé avec succès!", "Avatar_changed_successfully": "Avatar changé avec succès !",
"Avatar_Url": "URL de l'avatar", "Avatar_Url": "URL de l'avatar",
"Away": "absent", "Away": "Absent",
"Back": "Arrière", "Back": "Retour",
"Black": "Noir", "Black": "Noir",
"Block_user": "Bloquer l'Utilisateur", "Block_user": "Bloquer l'utilisateur",
"Browser": "Navigateur", "Browser": "Navigateur",
"Broadcast_channel_Description": "Seuls les utilisateurs autorisés peuvent écrire de nouveaux messages, mais les autres utilisateurs pourront répondre.", "Broadcast_channel_Description": "Seuls les utilisateurs autorisés peuvent écrire de nouveaux messages, mais les autres utilisateurs pourront répondre.",
"Broadcast_Channel": "Canal de diffusion", "Broadcast_Channel": "Canal de diffusion",
"Busy": "Occupé", "Busy": "Occupé",
"By_proceeding_you_are_agreeing": "En procédant, vous acceptez nos", "By_proceeding_you_are_agreeing": "En poursuivant, vous acceptez nos",
"Cancel_editing": "Annuler la modification", "Cancel_editing": "Annuler la modification",
"Cancel_recording": "Annuler l'enregistrement", "Cancel_recording": "Annuler l'enregistrement",
"Cancel": "Annuler", "Cancel": "Annuler",
"changing_avatar": "changer d'avatar", "changing_avatar": "changer d'avatar",
"creating_channel": "créer un canal", "creating_channel": "création d'un canal",
"creating_invite": "création d'une invitation", "creating_invite": "création d'une invitation",
"Channel_Name": "Nom du canal", "Channel_Name": "Nom du canal",
"Channels": "Canaux", "Channels": "Canaux",
"Chats": "Chats", "Chats": "Chats",
"Call_already_ended": "L'appel a déjà terminé !", "Call_already_ended": "Appel déjà terminé !",
"Click_to_join": "Cliquez pour rejoindre!", "Clear_cookies_alert": "Voulez-vous effacer tous les cookies ?",
"Clear_cookies_desc": "Cette action effacera tous les cookies de connexion ce qui vous permettra de vous connecter à d'autres comptes.",
"Clear_cookies_yes": "Oui, effacez les cookies",
"Clear_cookies_no": "Non, gardez les cookies",
"Click_to_join": "Cliquez pour rejoindre !",
"Close": "Fermer", "Close": "Fermer",
"Close_emoji_selector": "Fermer le sélecteur d'emoji", "Close_emoji_selector": "Fermer le sélecteur d'émoji",
"Closing_chat": "Fermeture du Salon de discussion", "Closing_chat": "Fermeture du chat",
"Change_language_loading": "Changement de la langue.", "Change_language_loading": "Changement de la langue.",
"Chat_closed_by_agent": "Le salon de discussion a été fermé", "Chat_closed_by_agent": "Chat fermé par l'agent",
"Choose": "Choisir", "Choose": "Choisir",
"Choose_from_library": "Choisissez parmi la bibliothèque", "Choose_from_library": "Choisissez dans la bibliothèque",
"Choose_file": "Choisir un fichier", "Choose_file": "Choisir le fichier",
"Choose_where_you_want_links_be_opened": "Choisissez ou vous souhaitez ouvrir vos liens", "Choose_where_you_want_links_be_opened": "Choisissez oµ vous souhaitez ouvrir les liens",
"Code": "Code", "Code": "Code",
"Code_or_password_invalid": "Code ou mot de passe invalide", "Code_or_password_invalid": "Code ou mot de passe invalide",
"Collaborative": "Collaborative", "Collaborative": "Collaboratif",
"Confirm": "Confirmer", "Confirm": "Confirmer",
"Connect": "Se connecter", "Connect": "Connecter",
"Connected": "Connecté", "Connected": "Connecté",
"connecting_server": "connexion en cours au serveur", "connecting_server": "connexion en cours au serveur",
"Connecting": "Connexion ...", "Connecting": "Connexion...",
"Contact_us": "Contactez nous", "Contact_us": "Contactez-nous",
"Contact_your_server_admin": "Contactez l'administrateur de votre serveur.", "Contact_your_server_admin": "Contactez votre administrateur de serveur.",
"Continue_with": "Continuer avec", "Continue_with": "Continuer avec",
"Copied_to_clipboard": "Copié dans le presse-papier!", "Copied_to_clipboard": "Copié dans le presse-papier !",
"Copy": "Copier", "Copy": "Copier",
"Conversation": "Conversation", "Conversation": "Conversation",
"Permalink": "Lien permanent", "Permalink": "Lien permanent",
"Certificate_password": "Mot de passe du Certificat", "Certificate_password": "Mot de passe du certificat",
"Clear_cache": "Effacer le cache local", "Clear_cache": "Effacer le cache du serveur local",
"Clear_cache_loading": "Effacement du cache.", "Clear_cache_loading": "Effacement du cache.",
"Whats_the_password_for_your_certificate": "Quel est le mot de passe du Certificat ?", "Whats_the_password_for_your_certificate": "Quel est le mot de passe de votre certificat ?",
"Create_account": "Créer un compte", "Create_account": "Créer un compte",
"Create_Channel": "Créer un canal", "Create_Channel": "Créer un canal",
"Create_Direct_Messages": "Créer un message direct", "Create_Direct_Messages": "Créer des messages directs",
"Create_Discussion": "Créer une Discussion", "Create_Discussion": "Créer une discussion",
"Created_snippet": "créé un extrait", "Created_snippet": "créé un extrait",
"Create_a_new_workspace": "Créer un nouvel espace de travail", "Create_a_new_workspace": "Créer un nouvel espace de travail",
"Create": "Créer", "Create": "Créer",
"Custom_Status": "Statut Personnalisé", "Custom_Status": "Statut personnalisé",
"Dark": "Sombre", "Dark": "Sombre",
"Dark_level": "Niveau d'assombrissement", "Dark_level": "Niveau d'obscurité",
"Default": "Défaut", "Default": "Défaut",
"Default_browser": "Navigateur par défaut", "Default_browser": "Navigateur par défaut",
"Delete_Room_Warning": "Supprimer une salle supprimera tous les messages postés dans la salle. Ça ne peut pas être annulé.", "Delete_Room_Warning": "Supprimer une salon supprimera tous les messages publiés dans le salon. Ça ne peut pas être annulé.",
"Department": "Département", "Department": "Département",
"delete": "supprimer", "delete": "supprimer",
"Delete": "Supprimer", "Delete": "Supprimer",
"DELETE": "SUPPRIMER", "DELETE": "SUPPRIMER",
"deleting_room": "effacement de la salle", "move": "déplacer",
"deleting_room": "suppression du salon",
"description": "la description", "description": "la description",
"Description": "La description", "Description": "La description",
"Desktop_Options": "Desktop Options", "Desktop_Options": "Options de bureau",
"Desktop_Notifications": "Notifications de bureau",
"Desktop_Alert_info": "Ces notifications sont transmises sur le bureau",
"Directory": "Répertoire", "Directory": "Répertoire",
"Direct_Messages": "Messages directs", "Direct_Messages": "Messages directs",
"Disable_notifications": "Désactiver les notifications", "Disable_notifications": "Désactiver les notifications",
"Discussions": "Discussions", "Discussions": "Discussions",
"Discussion_Desc": "Aide à garder un aperçu de ce qui se passe! En créant une discussion, un sous-canal de celui que vous avez sélectionné est créé et les deux sont liés.", "Discussion_Desc": "Aide à garder une vue d'ensemble sur ce qui se passe ! En créant une discussion, un sous-canal de celui que vous avez sélectionné est créé et les deux sont liés.",
"Discussion_name": "Nom de la discussion", "Discussion_name": "Nom de la discussion",
"Done": "Fait", "Done": "Fait",
"Dont_Have_An_Account": "Vous n'avez pas de compte?", "Dont_Have_An_Account": "Vous n'avez pas de compte ?",
"Do_you_have_an_account": "Avez-vous un compte?", "Do_you_have_an_account": "Avez-vous un compte ?",
"Do_you_have_a_certificate": "Avez-vous un certificat?", "Do_you_have_a_certificate": "Avez-vous un certificat ?",
"Do_you_really_want_to_key_this_room_question_mark": "Voulez-vous vraiment {{key}} cette salle?", "Do_you_really_want_to_key_this_room_question_mark": "Voulez-vous vraiment {{key}} ce salon ?",
"E2E_Encryption": "Cryptage E2E",
"E2E_How_It_Works_info1": "Vous pouvez désormais créer des groupes privés et des messages directs chiffrés. Vous pouvez également modifier les groupes privés ou DM existants pour les crypter.",
"E2E_How_It_Works_info2": "Il s'agit du *chiffrement de bout en bout*, la clé permettant de coder/décoder vos messages ne sera pas enregistrée sur le serveur. C'est pourquoi *vous devez stocker ce mot de passe à un endroit sûr* auquel vous pourrez accéder plus tard si vous en avez besoin.",
"E2E_How_It_Works_info3": "Si vous continuez, un mot de passe E2E sera automatiquement généré.",
"E2E_How_It_Works_info4": "Vous pouvez également configurer un nouveau mot de passe pour votre clé de cryptage à tout moment à partir de n'importe quel navigateur dans lequel vous avez entré le mot de passe E2E existant.",
"edit": "modifier", "edit": "modifier",
"edited": "édité", "edited": "modifié",
"Edit": "Modifier", "Edit": "Modifier",
"Edit_Status": "Modifier le Statut", "Edit_Status": "Modifier le statut",
"Edit_Invite": "Modifier l'invitation", "Edit_Invite": "Modifier l'invitation",
"End_to_end_encrypted_room": "Salon crypté de bout en bout",
"end_to_end_encryption": "chiffrement de bout en bout",
"Email_Notification_Mode_All": "Chaque mention/MD",
"Email_Notification_Mode_Disabled": "Désactivé",
"Email_or_password_field_is_empty": "Le champ e-mail ou mot de passe est vide", "Email_or_password_field_is_empty": "Le champ e-mail ou mot de passe est vide",
"Email": "E-mail", "Email": "E-mail",
"email": "e-mail", "email": "e-mail",
"Empty_title": "Titre vide", "Empty_title": "Titre vide",
"Enable_Auto_Translate": "Activer la traduction-auto", "Enable_Auto_Translate": "Activer la traduction automatique",
"Enable_notifications": "Activer les notifications", "Enable_notifications": "Activer les notifications",
"Encrypted": "Crypté",
"Encrypted_message": "Message crypté",
"Enter_Your_E2E_Password": "Entrez votre mot de passe E2E",
"Enter_Your_Encryption_Password_desc1": "Cela vous permettra d'accéder à vos groupes privés cryptés et à vos messages directs.",
"Enter_Your_Encryption_Password_desc2": "Vous devez entrer le mot de passe pour coder/décoder les messages à chaque endroit où vous utilisez le chat.",
"Encryption_error_title": "Votre mot de passe de cryptage semble erroné",
"Encryption_error_desc": "Il n'a pas été possible de décoder votre clé de cryptage pour être importé.",
"Everyone_can_access_this_channel": "Tout le monde peut accéder à ce canal", "Everyone_can_access_this_channel": "Tout le monde peut accéder à ce canal",
"Error_uploading": "Erreur lors du téléchargement", "Everyone_can_access_this_team": "Tout le monde peut accéder à cette équipe",
"Error_uploading": "Erreur lors de l'envoi",
"Expiration_Days": "Expiration (Jours)", "Expiration_Days": "Expiration (Jours)",
"Favorite": "Favoris", "Favorite": "Favori",
"Favorites": "Favoris", "Favorites": "Favoris",
"Files": "Fichiers", "Files": "Fichiers",
"File_description": "Description du fichier", "File_description": "Description du fichier",
@ -214,38 +241,40 @@
"Following_thread": "Suivre le fil", "Following_thread": "Suivre le fil",
"For_your_security_you_must_enter_your_current_password_to_continue": "Pour votre sécurité, vous devez entrer votre mot de passe actuel pour continuer.", "For_your_security_you_must_enter_your_current_password_to_continue": "Pour votre sécurité, vous devez entrer votre mot de passe actuel pour continuer.",
"Forgot_password_If_this_email_is_registered": "Si cet e-mail est enregistré, nous vous enverrons des instructions pour réinitialiser votre mot de passe. Si vous ne recevez pas d'e-mail sous peu, veuillez revenir et réessayer.", "Forgot_password_If_this_email_is_registered": "Si cet e-mail est enregistré, nous vous enverrons des instructions pour réinitialiser votre mot de passe. Si vous ne recevez pas d'e-mail sous peu, veuillez revenir et réessayer.",
"Forgot_password": "Mot de passe oublié", "Forgot_password": "Mot de passe oublié ?",
"Forgot_Password": "Mot de passe oublié", "Forgot_Password": "Mot de passe oublié",
"Forward": "Faire suivre", "Forward": "Transmettre",
"Forward_Chat": "Faire suivre le canal de discussion", "Forward_Chat": "Transmettre la conversation",
"Forward_to_department": "Faire suivre au département", "Forward_to_department": "Transmettre au département",
"Forward_to_user": "Faire suivre a l'utilisateur", "Forward_to_user": "Transmettre à l'utilisateur",
"Full_table": "Cliquez pour voir la table complète", "Full_table": "Cliquez pour voir le tableau complet",
"Generate_New_Link": "Générer un nouveau lien", "Generate_New_Link": "Générer un nouveau lien",
"Group_by_favorites": "Grouper par favoris", "Group_by_favorites": "Grouper par favoris",
"Group_by_type": "Grouper par type", "Group_by_type": "Grouper par type",
"Hide": "Cacher", "Hide": "Cacher",
"Has_joined_the_channel": "a rejoint le canal", "Has_joined_the_channel": "a rejoint le canal",
"Has_joined_the_conversation": "a rejoint la conversation", "Has_joined_the_conversation": "a rejoint la conversation",
"Has_left_the_channel": "a quitté la chaîne", "Has_left_the_channel": "a quitté le canal",
"Hide_System_Messages": "Masquer les messages système", "Hide_System_Messages": "Masquer les messages système",
"Hide_type_messages": "Masquer les messages \"{{type}}\"", "Hide_type_messages": "Masquer les messages \"{{type}}\"",
"How_It_Works": "Comment cela fonctionne",
"Message_HideType_uj": "L'utilisateur a rejoint", "Message_HideType_uj": "L'utilisateur a rejoint",
"Message_HideType_ul": "L'utilisateur est parti", "Message_HideType_ul": "L'utilisateur est parti",
"Message_HideType_ru": "Utilisateur éjecté", "Message_HideType_ru": "Utilisateur supprimé",
"Message_HideType_au": "Utilisateur ajouté", "Message_HideType_au": "Utilisateur ajouté",
"Message_HideType_mute_unmute": "Utilisateur rendu muet / a retrouvé la parole", "Message_HideType_mute_unmute": "Utilisateur rendu muet / a retrouvé la parole",
"Message_HideType_r": "Le nom du salon a été changé", "Message_HideType_r": "Nom du salon modifié",
"Message_HideType_ut": "L'Utilisateur a rejoint la conversation", "Message_HideType_ut": "L'utilisateur a rejoint la conversation",
"Message_HideType_wm": "Bienvenue", "Message_HideType_wm": "Bienvenue",
"Message_HideType_rm": "Message supprimé", "Message_HideType_rm": "Message supprimé",
"Message_HideType_subscription_role_added": "a été défini avec ce Rôle", "Message_HideType_subscription_role_added": "Rôle assigné",
"Message_HideType_subscription_role_removed": "Ce Rôle n'est plus défini", "Message_HideType_subscription_role_removed": "Le rôle n'est plus défini",
"Message_HideType_room_archived": "Salon Archivé", "Message_HideType_room_archived": "Salon archivé",
"Message_HideType_room_unarchived": "Salon Désarchivé", "Message_HideType_room_unarchived": "Salon désarchivé",
"I_Saved_My_E2E_Password": "J'ai enregistré mon mot de passe E2E",
"IP": "IP", "IP": "IP",
"In_app": "In-app", "In_app": "Dans l'app",
"In_App_And_Desktop": "In-app et Bureau", "In_App_And_Desktop": "Dans l'application et sur le bureau",
"In_App_and_Desktop_Alert_info": "Affiche une bannière en haut de l'écran lorsque l'application est ouverte et affiche une notification sur le bureau", "In_App_and_Desktop_Alert_info": "Affiche une bannière en haut de l'écran lorsque l'application est ouverte et affiche une notification sur le bureau",
"Invisible": "Invisible", "Invisible": "Invisible",
"Invite": "Inviter", "Invite": "Inviter",
@ -253,25 +282,29 @@
"is_not_a_valid_RocketChat_instance": "n'est pas une instance valide de Rocket.Chat", "is_not_a_valid_RocketChat_instance": "n'est pas une instance valide de Rocket.Chat",
"is_typing": "est en train d'écrire", "is_typing": "est en train d'écrire",
"Invalid_or_expired_invite_token": "Jeton d'invitation non valide ou expiré", "Invalid_or_expired_invite_token": "Jeton d'invitation non valide ou expiré",
"Invalid_server_version": "Le serveur que vous essayez de connecter utilise une version qui n'est plus prise en charge par l'application: {{currentVersion}}.\n\nNous exigeons la version {{minVersion}}", "Invalid_server_version": "Le serveur auquel vous essayez de vous connecter utilise une version qui n'est plus prise en charge par l'application : {{currentVersion}}.\n\nNous exigeons la version {{minVersion}}",
"Invite_Link": "Lien d'invitation", "Invite_Link": "Lien d'invitation",
"Invite_users": "Inviter utilisateur", "Invite_users": "Inviter des utilisateurs",
"Join": "Rejoindre", "Join": "Rejoindre",
"Join_Code": "Code d'adhésion",
"Insert_Join_Code": "Insérer le code d'adhésion",
"Join_our_open_workspace": "Rejoignez notre espace de travail ouvert", "Join_our_open_workspace": "Rejoignez notre espace de travail ouvert",
"Join_your_workspace": "Rejoignez votre espace de travail", "Join_your_workspace": "Rejoignez votre espace de travail",
"Just_invited_people_can_access_this_channel": "Seuls les invités peuvent accéder à ce canal", "Just_invited_people_can_access_this_channel": "Seuls les personnes invitées peuvent accéder à ce canal",
"Just_invited_people_can_access_this_team": "Seules les personnes invitées peuvent accéder à cette équipe",
"Language": "Langue", "Language": "Langue",
"last_message": "Dernier message", "last_message": "dernier message",
"Leave_channel": "Quitter le canal", "Leave_channel": "Quitter le canal",
"leaving_room": "En quittent le canal", "leaving_room": "quittant le salon",
"Leave": "Quitter",
"leave": "quitter", "leave": "quitter",
"Legal": "Légale", "Legal": "Légal",
"Light": "Lumière", "Light": "Clair",
"License": "Licence", "License": "Licence",
"Livechat": "Livechat", "Livechat": "Chat en direct",
"Livechat_edit": "Livechat modification", "Livechat_edit": "Modifier le chat en direct",
"Login": "Connexion", "Login": "Connexion",
"Login_error": "Vos identifiants ont été rejetés! Veuillez réessayer.", "Login_error": "Vos identifiants ont été rejetés ! Veuillez réessayer.",
"Login_with": "Se connecter avec", "Login_with": "Se connecter avec",
"Logging_out": "Déconnexion.", "Logging_out": "Déconnexion.",
"Logout": "Se déconnecter", "Logout": "Se déconnecter",
@ -282,7 +315,7 @@
"Mentioned_Messages": "Messages mentionnés", "Mentioned_Messages": "Messages mentionnés",
"mentioned": "mentionné", "mentioned": "mentionné",
"Mentions": "Mentions", "Mentions": "Mentions",
"Message_accessibility": "message de {{user}} à {{time}}: {{message}}", "Message_accessibility": "Message de {{user}} à {{time}} : {{message}}",
"Message_actions": "Actions de message", "Message_actions": "Actions de message",
"Message_pinned": "Message épinglé", "Message_pinned": "Message épinglé",
"Message_removed": "Message supprimé", "Message_removed": "Message supprimé",
@ -293,13 +326,14 @@
"Message": "Message", "Message": "Message",
"Messages": "Messages", "Messages": "Messages",
"Message_Reported": "Message signalé", "Message_Reported": "Message signalé",
"Microphone_Permission_Message": "Rocket.Chat doit avoir accès à votre microphone pour pouvoir envoyer un message audio.", "Microphone_Permission_Message": "Rocket.Chat a besoin d'accéder à votre microphone pour que vous puissiez envoyer un message audio.",
"Microphone_Permission": "Permission de microphone", "Microphone_Permission": "Permission de microphone",
"Mute": "Rendre muet", "Mute": "Rendre muet",
"muted": "Rendu muet", "muted": "muet",
"My_servers": "Mes serveurs", "My_servers": "Mes serveurs",
"N_people_reacted": "{{n}} personnes ont réagi", "N_people_reacted": "{{n}} personnes ont réagi",
"N_users": "{{n}} utilisateurs", "N_users": "{{n}} utilisateurs",
"N_channels": "{{n}} canaux",
"name": "nom", "name": "nom",
"Name": "Nom", "Name": "Nom",
"Navigation_history": "Historique de navigation", "Navigation_history": "Historique de navigation",
@ -313,26 +347,28 @@
"No_mentioned_messages": "Aucun message mentionné", "No_mentioned_messages": "Aucun message mentionné",
"No_pinned_messages": "Aucun message épinglé", "No_pinned_messages": "Aucun message épinglé",
"No_results_found": "Aucun résultat trouvé", "No_results_found": "Aucun résultat trouvé",
"No_starred_messages": "Pas de messages suivis", "No_starred_messages": "Aucun message suivi",
"No_thread_messages": "Aucun fil de discussion", "No_thread_messages": "Aucun message de fil de discussion",
"No_label_provided": "Aucun {{label}} fourni.", "No_label_provided": "Aucun {{label}} fourni.",
"No_Message": "Aucun message", "No_Message": "Aucun message",
"No_messages_yet": "Pas encore de messages", "No_messages_yet": "Pas encore de messages",
"No_Reactions": "Aucune réaction", "No_Reactions": "Aucune réaction",
"No_Read_Receipts": "Pas d'accusé de lecture", "No_Read_Receipts": "Aucun accusé de lecture",
"Not_logged": "Non connecté", "Not_logged": "Non connecté",
"Not_RC_Server": "Ce n'est pas un serveur Rocket.Chat.\n{{contact}}", "Not_RC_Server": "Ce n'est pas un serveur Rocket.Chat.\n{{contact}}",
"Nothing": "Rien", "Nothing": "Rien",
"Nothing_to_save": "Rien à enregistrer!", "Nothing_to_save": "Rien à enregistrer !",
"Notify_active_in_this_room": "Notifier les utilisateurs actifs dans cette salle", "Notify_active_in_this_room": "Notifier les utilisateurs actifs dans ce salon",
"Notify_all_in_this_room": "Notifier tous dans cette salle", "Notify_all_in_this_room": "Avertir tout le monde dans ce salon",
"Notifications": "Notifications", "Notifications": "Notifications",
"Notification_Duration": "Durée de Notification", "Notification_Duration": "Durée des notifications",
"Notification_Preferences": "Préférences de Notification", "Notification_Preferences": "Préférences de notification",
"No_available_agents_to_transfer": "Aucun agent disponible à qui transférer", "No_available_agents_to_transfer": "Aucun agent disponible pour le transfert",
"Offline": "Hors ligne", "Offline": "Hors ligne",
"Oops": "Oops!", "Oops": "Oups !",
"Omnichannel": "Omnichannel", "Omnichannel": "Omnicanal",
"Open_Livechats": "Discussions en cours",
"Omnichannel_enable_alert": "Vous n'êtes pas disponible sur Omnicanal. Souhaitez-vous être disponible ?",
"Onboarding_description": "Un espace de travail est l'espace de collaboration de votre équipe ou organisation. Demandez à l'administrateur de l'espace de travail l'adresse pour rejoindre ou créez-en une pour votre équipe.", "Onboarding_description": "Un espace de travail est l'espace de collaboration de votre équipe ou organisation. Demandez à l'administrateur de l'espace de travail l'adresse pour rejoindre ou créez-en une pour votre équipe.",
"Onboarding_join_workspace": "Rejoindre un espace de travail", "Onboarding_join_workspace": "Rejoindre un espace de travail",
"Onboarding_subtitle": "Au-delà de la collaboration d'équipe", "Onboarding_subtitle": "Au-delà de la collaboration d'équipe",
@ -343,15 +379,15 @@
"Onboarding_more_options": "Plus d'options", "Onboarding_more_options": "Plus d'options",
"Online": "En ligne", "Online": "En ligne",
"Only_authorized_users_can_write_new_messages": "Seuls les utilisateurs autorisés peuvent écrire de nouveaux messages.", "Only_authorized_users_can_write_new_messages": "Seuls les utilisateurs autorisés peuvent écrire de nouveaux messages.",
"Open_emoji_selector": "Ouvrir sélecteur emoji", "Open_emoji_selector": "Ouvrir le sélecteur d'émoji",
"Open_Source_Communication": "Communication Open Source", "Open_Source_Communication": "Communication Open Source",
"Open_your_authentication_app_and_enter_the_code": "Ouvrez votre application d'authentification et entrez le code.", "Open_your_authentication_app_and_enter_the_code": "Ouvrez votre application d'authentification et entrez le code.",
"OR": "OU", "OR": "OU",
"OS": "OS", "OS": "OS",
"Overwrites_the_server_configuration_and_use_room_config": "Écrase la configuration du serveur et utilise la configuration du salon", "Overwrites_the_server_configuration_and_use_room_config": "Écrase la configuration du serveur et utilise la configuration du salon",
"Password": "Mot de passe", "Password": "Mot de passe",
"Parent_channel_or_group": "Chaîne ou groupe parent", "Parent_channel_or_group": "Canal ou groupe parent",
"Permalink_copied_to_clipboard": "Lien permanent copié dans le presse-papier!", "Permalink_copied_to_clipboard": "Lien permanent copié dans le presse-papiers !",
"Phone": "Téléphone", "Phone": "Téléphone",
"Pin": "Épingler", "Pin": "Épingler",
"Pinned_Messages": "Messages épinglés", "Pinned_Messages": "Messages épinglés",
@ -359,20 +395,20 @@
"Pinned": "Épinglé", "Pinned": "Épinglé",
"Please_add_a_comment": "Veuillez ajouter un commentaire", "Please_add_a_comment": "Veuillez ajouter un commentaire",
"Please_enter_your_password": "Veuillez entrer votre mot de passe", "Please_enter_your_password": "Veuillez entrer votre mot de passe",
"Please_wait": "Attendez s'il vous plaît", "Please_wait": "Veuillez patienter.",
"Preferences": "Préférences", "Preferences": "Préférences",
"Preferences_saved": "Préférences sauvegardées!", "Preferences_saved": "Préférences sauvegardées !",
"Privacy_Policy": " Politique de confidentialité", "Privacy_Policy": " Politique de confidentialité",
"Private_Channel": "Canal privé", "Private_Channel": "Canal privé",
"Private_Groups": "Groupes privés", "Private_Groups": "Groupes privés",
"Private": "Privé", "Private": "Privé",
"Processing": "En traitement...", "Processing": "Traitement...",
"Profile_saved_successfully": "Profil enregistré avec succès!", "Profile_saved_successfully": "Profil enregistré avec succès !",
"Profile": "Profil", "Profile": "Profil",
"Public_Channel": "Canal Public", "Public_Channel": "Canal public",
"Public": "Public", "Public": "Public",
"Push_Notifications": "Notifications Push", "Push_Notifications": "Notifications Push",
"Push_Notifications_Alert_Info": "Ces notifications vous sont livrées lorsque l'application n'est pas ouverte", "Push_Notifications_Alert_Info": "Ces notifications vous sont envoyées lorsque l'application n'est pas ouverte",
"Quote": "Citation", "Quote": "Citation",
"Reactions_are_disabled": "Les réactions sont désactivées", "Reactions_are_disabled": "Les réactions sont désactivées",
"Reactions_are_enabled": "Les réactions sont activées", "Reactions_are_enabled": "Les réactions sont activées",
@ -380,61 +416,68 @@
"Read": "Lecture", "Read": "Lecture",
"Read_External_Permission_Message": "Rocket.Chat doit accéder aux photos, aux médias et aux fichiers sur votre appareil", "Read_External_Permission_Message": "Rocket.Chat doit accéder aux photos, aux médias et aux fichiers sur votre appareil",
"Read_External_Permission": "Permission de lecture des fichiers", "Read_External_Permission": "Permission de lecture des fichiers",
"Read_Only_Channel": "Chaîne en lecture seule", "Read_Only_Channel": "Canal en lecture seule",
"Read_Only": "Lecture seule", "Read_Only": "Lecture seule",
"Read_Receipt": "Accusé de réception", "Read_Receipt": "Accusé de réception",
"Receive_Group_Mentions": "Recevoir des mentions de groupe", "Receive_Group_Mentions": "Recevoir des mentions de groupe",
"Receive_Group_Mentions_Info": "Recevoir les mentions @all et @here", "Receive_Group_Mentions_Info": "Recevoir des mentions @all et @here",
"Register": "S'inscrire", "Register": "S'inscrire",
"Repeat_Password": "Répéter le mot de passe", "Repeat_Password": "Répéter le mot de passe",
"Replied_on": "Répondu le:", "Replied_on": "A répondu le :",
"replies": "réponses", "replies": "réponses",
"reply": "répondre", "reply": "répondre",
"Reply": "Répondre", "Reply": "Répondre",
"Report": "Signaler", "Report": "Signaler",
"Receive_Notification": "Recevoir une notification", "Receive_Notification": "Recevoir une notification",
"Receive_notifications_from": "Recevoir les notifications de {{name}}", "Receive_notifications_from": "Recevoir des notifications de {{name}}",
"Resend": "Renvoyer", "Resend": "Renvoyer",
"Reset_password": "Réinitialiser le mot de passe", "Reset_password": "Réinitialiser le mot de passe",
"resetting_password": "réinitialisation du mot de passe", "resetting_password": "réinitialisation du mot de passe",
"RESET": "RÉINITIALISER", "RESET": "RÉINITIALISER",
"Return": "Retour", "Return": "Retour",
"Review_app_title": "Appréciez-vous cette application?", "Review_app_title": "Appréciez-vous cette application ?",
"Review_app_desc": "Donnez-nous 5 étoiles sur {{store}}", "Review_app_desc": "Donnez-nous 5 étoiles sur {{store}}",
"Review_app_yes": "Bien sur!", "Review_app_yes": "Bien sûr !",
"Review_app_no": "Non", "Review_app_no": "Non",
"Review_app_later": "plus tard", "Review_app_later": "Peut-être plus tard",
"Review_app_unable_store": "Impossible d'ouvrir {{store}}", "Review_app_unable_store": "Impossible d'ouvrir {{store}}",
"Review_this_app": "Donnez votre avis sur cette application", "Review_this_app": "Donnez votre avis sur cette application",
"Remove": "Retirer", "Remove": "Supprimer",
"remove": "supprimer",
"Roles": "Rôles", "Roles": "Rôles",
"Room_actions": "Actions de canal", "Room_actions": "Actions du salon",
"Room_changed_announcement": "Annonce de canal est changée en: {{announcement}} par {{userBy}}", "Room_changed_announcement": "Annonce du salon changé en : {{announcement}} par {{userBy}}",
"Room_changed_description": "Description de canal est changée en: {{description}} par {{userBy}}", "Room_changed_avatar": "Avatar du salon modifié par {{userBy}}",
"Room_changed_privacy": "Type de canal est changé en: {{type}} par {{userBy}}", "Room_changed_description": "Description du salon changé en : {{description}} par {{userBy}}",
"Room_changed_topic": "Le sujet de canal est changé en: {{topic}} par {{userBy}}", "Room_changed_privacy": "Type de salon changé en : {{type}} par {{userBy}}",
"Room_Files": "Fichiers de canal", "Room_changed_topic": "Le sujet de salon est changé en : {{topic}} par {{userBy}}",
"Room_Info_Edit": "Infos sur le canal Modifier", "Room_Files": "Fichiers du salon",
"Room_Info": "Info sur le canal", "Room_Info_Edit": "Modifier les informations du salon",
"Room_Members": "Membres de canal", "Room_Info": "Info sur le salon",
"Room_name_changed": "Nom de canal est changé en: {{name}} par {{userBy}}", "Room_Members": "Membres du salon",
"SAVE": "ENREGISTRER", "Room_name_changed": "Nom de salon changé en : {{name}} par {{userBy}}",
"SAVE": "SAUVEGARDER",
"Save_Changes": "Sauvegarder les modifications", "Save_Changes": "Sauvegarder les modifications",
"Save": "Sauvegarder", "Save": "Sauvegarder",
"Saved": "Sauvé", "Saved": "Enregistré",
"saving_preferences": "sauvegardant les préférences", "saving_preferences": "enregistrement des préférences",
"saving_profile": "enregistrement du profil", "saving_profile": "enregistrement du profil",
"saving_settings": "enregistrement des paramètres", "saving_settings": "enregistrement des paramètres",
"saved_to_gallery": "Sauvé dans la galerie", "saved_to_gallery": "Enregistré dans la galerie",
"Save_Your_E2E_Password": "Enregistrez votre mot de passe E2E",
"Save_Your_Encryption_Password": "Enregistrez votre mot de passe de cryptage",
"Save_Your_Encryption_Password_warning": "Ce mot de passe n'est stocké nulle part, enregistrez-le donc soigneusement ailleurs.",
"Save_Your_Encryption_Password_info": "Si vous perdez le mot de passe, il n'y a aucun moyen de le récupérer et vous perdrez l'accès à vos messages.",
"Search_Messages": "Rechercher des messages", "Search_Messages": "Rechercher des messages",
"Search": "Recherche", "Search": "Recherche",
"Search_by": "Recherche par", "Search_by": "Rechercher par",
"Search_global_users": "Rechercher des utilisateurs mondiaux", "Search_global_users": "Rechercher des utilisateurs mondiaux",
"Search_global_users_description": "Si vous activez, vous pouvez rechercher n'importe quel utilisateur d'autres sociétés ou serveurs.", "Search_global_users_description": "Si vous activez, vous pouvez rechercher n'importe quel utilisateur d'autres sociétés ou serveurs.",
"Seconds": "{{second}} secondes", "Seconds": "{{second}} secondes",
"Security_and_privacy": "Sécurité et vie privée",
"Select_Avatar": "Sélectionnez un avatar", "Select_Avatar": "Sélectionnez un avatar",
"Select_Server": "Sélectionnez un serveur", "Select_Server": "Sélectionnez un serveur",
"Select_Users": "Sélectionner des utilisateurs", "Select_Users": "Sélectionner les utilisateurs",
"Select_a_Channel": "Sélectionnez un canal", "Select_a_Channel": "Sélectionnez un canal",
"Select_a_Department": "Sélectionnez un département", "Select_a_Department": "Sélectionnez un département",
"Select_an_option": "Sélectionnez une option", "Select_an_option": "Sélectionnez une option",
@ -449,50 +492,50 @@
"Sent_an_attachment": "Envoyé une pièce jointe", "Sent_an_attachment": "Envoyé une pièce jointe",
"Server": "Serveur", "Server": "Serveur",
"Servers": "Serveurs", "Servers": "Serveurs",
"Server_version": "Version du serveur: {{version}}", "Server_version": "Version du serveur : {{version}}",
"Set_username_subtitle": "Le nom d'utilisateur est utilisé pour permettre aux autres de vous mentionner dans les messages", "Set_username_subtitle": "Le nom d'utilisateur est utilisé pour permettre aux autres de vous mentionner dans les messages",
"Set_custom_status": "Définir un statut personnalisé", "Set_custom_status": "Définir le statut personnalisé",
"Set_status": "Définir le statut", "Set_status": "Définir le statut",
"Status_saved_successfully": "Statut enregistré avec succès!", "Status_saved_successfully": "Statut enregistré avec succès !",
"Settings": "Paramètres", "Settings": "Paramètres",
"Settings_succesfully_changed": "Paramètres modifiés avec succès!", "Settings_succesfully_changed": "Paramètres modifiés avec succès !",
"Share": "Partager", "Share": "Partager",
"Share_Link": "Partager le lien", "Share_Link": "Partager le lien",
"Share_this_app": "Partager cette application", "Share_this_app": "Partager cette application",
"Show_more": "Afficher plus..", "Show_more": "Afficher plus..",
"Show_Unread_Counter": "Afficher le compteur non lu", "Show_Unread_Counter": "Afficher le compteur non lu",
"Show_Unread_Counter_Info": "Le compteur non-lu est affiché sous forme de badge à droite de la chaîne, dans la liste", "Show_Unread_Counter_Info": "Le compteur non lu est affiché sous forme de badge à droite du canal, dans la liste",
"Sign_in_your_server": "Connectez-vous à votre serveur", "Sign_in_your_server": "Connectez-vous à votre serveur",
"Sign_Up": "S'inscrire", "Sign_Up": "S'inscrire",
"Some_field_is_invalid_or_empty": "Certains champs sont invalides ou vides", "Some_field_is_invalid_or_empty": "Certains champs sont invalides ou vides",
"Sorting_by": "Tri par {{key}}", "Sorting_by": "Tri par {{key}}",
"Sound": "Son", "Sound": "Son",
"Star_room": "Favoriser canal", "Star_room": "Canal favoris",
"Star": "Favoris", "Star": "Mettre en favoris",
"Starred_Messages": "Les messages favorisé", "Starred_Messages": "Les messages favoris",
"starred": "favorisé", "starred": "favoris",
"Starred": "Favorisé", "Starred": "Favoris",
"Start_of_conversation": "Début de conversation", "Start_of_conversation": "Début de conversation",
"Start_a_Discussion": "Lancer une discussion", "Start_a_Discussion": "Lancer une discussion",
"Started_discussion": "A commencé une discussion:", "Started_discussion": "A commencé une discussion :",
"Started_call": "Appel lancé par {{userBy}}", "Started_call": "Appel lancé par {{userBy}}",
"Submit": "Soumettre", "Submit": "Soumettre",
"Table": "Table", "Table": "Tableau",
"Tags": "Mots clés", "Tags": "Mots clés",
"Take_a_photo": "Prendre une photo", "Take_a_photo": "Prendre une photo",
"Take_a_video": "Prendre une vidéo", "Take_a_video": "Prendre une vidéo",
"Take_it": "Prends-le!", "Take_it": "Prends-le !",
"tap_to_change_status": "Appuyez pour changer de statut", "tap_to_change_status": "appuyez pour changer de statut",
"Tap_to_view_servers_list": "Appuyez pour afficher la liste des serveurs", "Tap_to_view_servers_list": "Appuyez pour afficher la liste des serveurs",
"Terms_of_Service": " Conditions d'utilisation ", "Terms_of_Service": " Conditions d'utilisation ",
"Theme": "Thème", "Theme": "Thème",
"The_user_wont_be_able_to_type_in_roomName": "L'utilisateur ne pourra pas écrire dans {{roomName}}", "The_user_wont_be_able_to_type_in_roomName": "L'utilisateur ne pourra pas écrire dans {{roomName}}",
"The_user_will_be_able_to_type_in_roomName": "L'utilisateur pourra écrire dans {{roomName}}", "The_user_will_be_able_to_type_in_roomName": "L'utilisateur pourra écrire dans {{roomName}}",
"There_was_an_error_while_action": "Il y avait une erreur en {{action}}!", "There_was_an_error_while_action": "Une erreur s'est produite lors de {{action}} !",
"This_room_is_blocked": "Cette canal est bloquée", "This_room_is_blocked": "Ce salon est bloqué",
"This_room_is_read_only": "Cette canal est en lecture seule", "This_room_is_read_only": "Ce salon est en lecture seule",
"Thread": "Fil de discutions", "Thread": "Fil de discussion",
"Threads": "Fils de discutions", "Threads": "Fils de discussions",
"Timezone": "Fuseau horaire", "Timezone": "Fuseau horaire",
"To": "A", "To": "A",
"topic": "sujet", "topic": "sujet",
@ -506,101 +549,103 @@
"Unblock_user": "Débloquer l'utilisateur", "Unblock_user": "Débloquer l'utilisateur",
"Unfavorite": "Supprimer des favoris", "Unfavorite": "Supprimer des favoris",
"Unfollowed_thread": "Ne plus suivre ce fil", "Unfollowed_thread": "Ne plus suivre ce fil",
"Unmute": "Rendre La parole", "Unmute": "Rendre la parole",
"unmuted": "Rendu la parole", "unmuted": "rendu la parole",
"Unpin": "Détacher", "Unpin": "Détacher",
"unread_messages": "non lus", "unread_messages": "non lu",
"Unread": "Non lu", "Unread": "Non lu",
"Unread_on_top": "Non lu sur le dessus", "Unread_on_top": "Non lu en haut",
"Unstar": "Unstar", "Unstar": "Enlever des favoris",
"Updating": "Mise à jour...", "Updating": "Mise à jour...",
"Uploading": "Téléchargement", "Uploading": "Envoyer",
"Upload_file_question_mark": "Télécharger le fichier?", "Upload_file_question_mark": "Téléverser un fichier ?",
"User": "Utilisateur", "User": "Utilisateur",
"Users": "Utilisateurs", "Users": "Utilisateurs",
"User_added_by": "L'utilisateur {{userAdded}} a été ajouté par {{userBy}}", "User_added_by": "Utilisateur {{userAdded}} ajouté par {{userBy}}",
"User_Info": "Info d'utilisateur", "User_Info": "Info d'utilisateur",
"User_has_been_key": "L'utilisateur a été {{key}}", "User_has_been_key": "L'utilisateur a été {{key}}",
"User_is_no_longer_role_by_": "{{user}} n'est plus {{role}} par {{userBy}}", "User_is_no_longer_role_by_": "{{user}} n'est plus {{role}} par {{userBy}}",
"User_muted_by": "L'utilisateur {{userMuted}} a été rendu muet par {{userBy}}", "User_muted_by": "L'utilisateur {{userMuted}} a été rendu muet par {{userBy}}",
"User_removed_by": "L'utilisateur {{userRemoved}} a été retiré par {{userBy}}", "User_removed_by": "Utilisateur {{userRemoved}} supprimé par {{userBy}}",
"User_sent_an_attachment": "{{user}} envoyé une pièce jointe", "User_sent_an_attachment": "{{user}} a envoyé une pièce jointe",
"User_unmuted_by": "L'utilisateur {{userBy}} a rendu la parole a {{userUnmuted}}", "User_unmuted_by": "L'utilisateur {{userBy}} a rendu la parole à {{userUnmuted}}",
"User_was_set_role_by_": "{{user}} l'utilisateur a été défini {{role}} par {{userBy}}", "User_was_set_role_by_": "{{user}} a été défini {{role}} par {{userBy}}",
"Username_is_empty": "Nom d'utilisateur est vide", "Username_is_empty": "Nom d'utilisateur est vide",
"Username": "Nom d'utilisateur", "Username": "Nom d'utilisateur",
"Username_or_email": "Nom d'utilisateur ou address e-mail", "Username_or_email": "Nom d'utilisateur ou e-mail",
"Uses_server_configuration": "Utilise la configuration du serveur", "Uses_server_configuration": "Utilise la configuration du serveur",
"Validating": "Validation", "Validating": "Validation",
"Registration_Succeeded": "Inscription réussie!", "Registration_Succeeded": "Inscription réussie !",
"Verify": "Vérifier", "Verify": "Vérifier",
"Verify_email_title": "Inscription réussie!", "Verify_email_title": "Inscription réussie !",
"Verify_email_desc": "Nous vous avons envoyé un e-mail pour confirmer votre inscription. Si vous ne recevez pas d'e-mail sous peu, veuillez revenir et réessayer.", "Verify_email_desc": "Nous vous avons envoyé un e-mail pour confirmer votre inscription. Si vous ne recevez pas d'e-mail sous peu, veuillez revenir et réessayer.",
"Verify_your_email_for_the_code_we_sent": "Vérifiez votre e-mail pour le code que nous avons envoyé", "Verify_your_email_for_the_code_we_sent": "Vérifiez votre e-mail pour le code que nous avons envoyé",
"Video_call": "Appel vidéo", "Video_call": "Appel vidéo",
"View_Original": "Voir l'original", "View_Original": "Voir l'original",
"Voice_call": "Appel vocal", "Voice_call": "Appel vocal",
"Waiting_for_network": "En attente du réseau ...", "Waiting_for_network": "En attente du réseau...",
"Websocket_disabled": "Le Websocket est désactivé pour ce serveur.\n{{contact}}", "Websocket_disabled": "Le Websocket est désactivé pour ce serveur.\n{{contact}}",
"Welcome": "Bienvenue", "Welcome": "Bienvenue",
"What_are_you_doing_right_now": "Qu'es ce que vous faites actuellement?", "What_are_you_doing_right_now": "Que fais-tu en ce moment ?",
"Whats_your_2fa": "Quel est votre code 2FA?", "Whats_your_2fa": "Quel est votre code 2FA ?",
"Without_Servers": "Sans serveurs", "Without_Servers": "Sans serveurs",
"Workspaces": "Espaces de travail", "Workspaces": "Espaces de travail",
"Would_you_like_to_return_the_inquiry": "Souhaitez-vous retourner la demande?", "Would_you_like_to_return_the_inquiry": "Souhaitez-vous retourner la demande ?",
"Write_External_Permission_Message": "Rocket.Chat a besoin d'accéder à votre galerie pour que vous puissiez enregistrer des images.", "Write_External_Permission_Message": "Rocket.Chat a besoin d'accéder à votre galerie pour que vous puissiez enregistrer des images.",
"Write_External_Permission": "Autorisation de la galerie", "Write_External_Permission": "Autorisation de la galerie",
"Yes": "Oui", "Yes": "Oui",
"Yes_action_it": "Oui, {{action}} le!", "Yes_action_it": "Oui, {{action}} le !",
"Yesterday": "Hier", "Yesterday": "Hier",
"You_are_in_preview_mode": "Vous êtes en mode de prévisualisation", "You_are_in_preview_mode": "Vous êtes en mode aperçu",
"You_are_offline": "Vous êtes hors ligne", "You_are_offline": "Vous êtes hors ligne",
"You_can_search_using_RegExp_eg": "Vous pouvez rechercher à l'aide de RegExp. e.g. `/^text$/i`", "You_can_search_using_RegExp_eg": "Vous pouvez utiliser RegExp., par exemple `/^texte$/i`",
"You_colon": "Vous: ", "You_colon": "Vous: ",
"you_were_mentioned": "vous avez été mentionné", "you_were_mentioned": "vous avez été mentionné",
"You_were_removed_from_channel": "Vous avez été retiré de{{channel}}", "You_were_removed_from_channel": "Vous avez été retiré de {{channel}}",
"you": "vous", "you": "vous",
"You": "Vous", "You": "Vous",
"Logged_out_by_server": "Vous avez été déconnecté par le serveur. Veuillez vous reconnecter.", "Logged_out_by_server": "Vous avez été déconnecté du serveur. Veuillez vous reconnecter.",
"You_need_to_access_at_least_one_RocketChat_server_to_share_something": "Vous devez accéder à au moins un serveur Rocket.Chat pour partager quelque chose.", "You_need_to_access_at_least_one_RocketChat_server_to_share_something": "Vous devez accéder à au moins un serveur Rocket.Chat pour partager quelque chose.",
"Your_certificate": "Votre Certificat", "You_need_to_verifiy_your_email_address_to_get_notications": "Vous devez vérifier votre adresse e-mail pour recevoir des notifications",
"Your_certificate": "Votre certificat",
"Your_invite_link_will_expire_after__usesLeft__uses": "Votre lien d'invitation expirera après {{usesLeft}} utilisations.", "Your_invite_link_will_expire_after__usesLeft__uses": "Votre lien d'invitation expirera après {{usesLeft}} utilisations.",
"Your_invite_link_will_expire_on__date__or_after__usesLeft__uses": "Votre lien d'invitation expirera le {{date}} ou après {{usesLeft}} utilisations.", "Your_invite_link_will_expire_on__date__or_after__usesLeft__uses": "Votre lien d'invitation expirera le {{date}} ou après {{usesLeft}} utilisations.",
"Your_invite_link_will_expire_on__date__": "Votre lien d'invitation expirera le {{date}}.", "Your_invite_link_will_expire_on__date__": "Votre lien d'invitation expirera le {{date}}.",
"Your_invite_link_will_never_expire": "Votre lien d'invitation n'expirera jamais.", "Your_invite_link_will_never_expire": "Votre lien d'invitation n'expirera jamais.",
"Your_workspace": "Votre espace de travail", "Your_workspace": "Votre espace de travail",
"Version_no": "Version: {{version}}", "Your_password_is": "Votre mot de passe est",
"You_will_not_be_able_to_recover_this_message": "Vous ne pourrez pas récupérer ce message!", "Version_no": "Version : {{version}}",
"You_will_unset_a_certificate_for_this_server": "Vous allez annuler un certificat pour ce serveur", "You_will_not_be_able_to_recover_this_message": "Vous ne pourrez pas récupérer ce message !",
"Change_Language": "Changer la Langue", "You_will_unset_a_certificate_for_this_server": "Vous allez supprimer un certificat pour ce serveur",
"Crash_report_disclaimer": "Nous ne suivons jamais le contenu de vos chats. Le rapport de plantage ne contient que des informations pertinentes pour nous afin d'identifier les problèmes et de les résoudre.", "Change_Language": "Changer la langue",
"Type_message": "Écrire un message", "Crash_report_disclaimer": "Nous ne suivons jamais le contenu de vos chats. Le rapport d'incident et les évènements d'analyse ne contiennent que des informations pertinentes pour nous afin d'identifier et de résoudre les problèmes.",
"Room_search": "Recherche de salon", "Type_message": "Tapez le message",
"Room_selection": "Sélection du Salon 1...9", "Room_search": "Recherche de salons",
"Next_room": "Salon Suivant", "Room_selection": "Sélection de salon 1...9",
"Previous_room": "Salon Précédent", "Next_room": "Salon suivant",
"Previous_room": "Salon précédent",
"New_room": "Nouveau salon", "New_room": "Nouveau salon",
"Upload_room": "Envoyer sur un salon", "Upload_room": "Envoyer dans un salon",
"Search_messages": "Recherche de messages", "Search_messages": "Rechercher des messages",
"Scroll_messages": "Défiler messages", "Scroll_messages": "Faire défiler les messages",
"Reply_latest": "Répondre au dernier", "Reply_latest": "Répondre au dernier",
"Reply_in_Thread": "Répondre dans le fil", "Reply_in_Thread": "Répondre dans le fil",
"Server_selection": "Sélection du serveur", "Server_selection": "Sélection du serveur",
"Server_selection_numbers": "Sélection du Serveur 1...9", "Server_selection_numbers": "Sélection du serveur 1...9",
"Add_server": "Ajouter serveur", "Add_server": "Ajouter un serveur",
"New_line": "Nouvelle ligne", "New_line": "Nouvelle ligne",
"You_will_be_logged_out_of_this_application": "Vous serez déconnecté de cette application.", "You_will_be_logged_out_of_this_application": "Vous serez déconnecté de cette application.",
"Clear": "Effacer", "Clear": "Effacer",
"This_will_clear_all_your_offline_data": "Cela effacera toutes vos données hors-ligne.", "This_will_clear_all_your_offline_data": "Cela effacera toutes vos données hors ligne.",
"This_will_remove_all_data_from_this_server": "Cela supprimera toutes les données de ce serveur.", "This_will_remove_all_data_from_this_server": "Cela supprimera toutes les données de ce serveur.",
"Mark_unread": "Marquer comme non lu", "Mark_unread": "Marquer comme non lu",
"Wait_activation_warning": "Avant de pouvoir vous connecter, votre compte doit être activé manuellement par un administrateur.", "Wait_activation_warning": "Avant de pouvoir vous connecter, votre compte doit être activé manuellement par un administrateur.",
"Screen_lock": "Verrouillage d'écran", "Screen_lock": "Verrouillage d'écran",
"Local_authentication_biometry_title": "Authentifier", "Local_authentication_biometry_title": "Authentifier",
"Local_authentication_biometry_fallback": "Utiliser le mot de passe", "Local_authentication_biometry_fallback": "Utiliser le code d'accès",
"Local_authentication_unlock_option": "Déverrouiller avec mot de passe", "Local_authentication_unlock_option": "Déverrouiller avec le code d'accès",
"Local_authentication_change_passcode": "Changer le code", "Local_authentication_change_passcode": "Changer le code d'accès",
"Local_authentication_info": "Remarque: si vous oubliez le code, vous devrez supprimer et réinstaller l'application.", "Local_authentication_info": "Remarque : si vous oubliez le code d'accès, vous devrez supprimer et réinstaller l'application.",
"Local_authentication_facial_recognition": "reconnaissance faciale", "Local_authentication_facial_recognition": "reconnaissance faciale",
"Local_authentication_fingerprint": "empreinte digitale", "Local_authentication_fingerprint": "empreinte digitale",
"Local_authentication_unlock_with_label": "Déverrouiller avec {{label}}", "Local_authentication_unlock_with_label": "Déverrouiller avec {{label}}",
@ -609,15 +654,112 @@
"Local_authentication_auto_lock_900": "Après 15 minutes", "Local_authentication_auto_lock_900": "Après 15 minutes",
"Local_authentication_auto_lock_1800": "Après 30 minutes", "Local_authentication_auto_lock_1800": "Après 30 minutes",
"Local_authentication_auto_lock_3600": "Après 1 heure", "Local_authentication_auto_lock_3600": "Après 1 heure",
"Passcode_enter_title": "Entrez votre mot de passe", "Passcode_enter_title": "Entrez votre code d'accès",
"Passcode_choose_title": "Choisissez votre nouveau mot de passe", "Passcode_choose_title": "Choisissez votre nouveau code d'accès",
"Passcode_choose_confirm_title": "Confirmez votre nouveau mot de passe", "Passcode_choose_confirm_title": "Confirmez votre nouveau code d'accès",
"Passcode_choose_error": "Les codes secrets ne correspondent pas. Réessayer.", "Passcode_choose_error": "Les codes d'accès ne correspondent pas. Réessayer.",
"Passcode_choose_force_set": "Code d'accès requis par l'administrateur", "Passcode_choose_force_set": "Code d'accès requis par l'administrateur",
"Passcode_app_locked_title": "App verrouillée", "Passcode_app_locked_title": "App verrouillée",
"Passcode_app_locked_subtitle": "Réessayez dans {{timeLeft}} secondes", "Passcode_app_locked_subtitle": "Réessayez dans {{timeLeft}} secondes",
"After_seconds_set_by_admin": "Après {{seconds}} secondes (défini par l'administrateur)", "After_seconds_set_by_admin": "Après {{seconds}} secondes (défini par l'administrateur)",
"Dont_activate": "Ne pas activer maintenant", "Dont_activate": "Ne pas activer maintenant",
"Queued_chats": "Discussions en file d'attente", "Queued_chats": "Discussions en file d'attente",
"Queue_is_empty": "La file d'attente est vide" "Queue_is_empty": "La file d'attente est vide",
"Logout_from_other_logged_in_locations": "Déconnexion des autres emplacements connectés",
"You_will_be_logged_out_from_other_locations": "Vous serez déconnecté des autres emplacements.",
"Logged_out_of_other_clients_successfully": "Déconnexion réussie des autres clients",
"Logout_failed": "Echec de la déconnexion !",
"Log_analytics_events": "Journal des événements d'analyse",
"E2E_encryption_change_password_title": "Changer le mot de passe de cryptage",
"E2E_encryption_change_password_description": "Vous pouvez désormais créer des groupes privés et des messages directs chiffrés. Vous pouvez également modifier les groupes privés ou DM existants pour les crypter.\nIl s'agit du chiffrement de bout en bout, la clé permettant de coder/décoder vos messages ne sera pas enregistrée sur le serveur. Pour cette raison, vous devez stocker ce mot de passe à un endroit sûr. Vous devrez le saisir sur les autres appareils sur lesquels vous souhaitez utiliser le cryptage E2E.",
"E2E_encryption_change_password_error": "Erreur lors de la modification du mot de passe de la clé E2E",
"E2E_encryption_change_password_success": "Le mot de passe de la clé E2E a été changé avec succès !",
"E2E_encryption_change_password_message": "Assurez-vous de l'avoir enregistré soigneusement ailleurs.",
"E2E_encryption_change_password_confirmation": "Oui, changez-le",
"E2E_encryption_reset_title": "Réinitialiser la clé E2E",
"E2E_encryption_reset_description": "Cette option supprimera la clé E2E actuelle et vous déconnectera.\nLorsque vous vous reconnecterez, Rocket.Chat générera une nouvelle clé et restaurera votre accès aux salons cryptés qui a un ou plusieurs membres en ligne.\nEn raison de la nature du cryptage E2E, Rocket.Chat ne pourra pas restaurer l'accès à un salon crypté qui n'a aucun membre en ligne.",
"E2E_encryption_reset_button": "Réinitialiser la clé E2E",
"E2E_encryption_reset_error": "Erreur lors de la réinitialisation de la clé E2E !",
"E2E_encryption_reset_message": "Vous allez être déconnecté.",
"E2E_encryption_reset_confirmation": "Oui, réinitialisez-le",
"Following": "Suivant",
"Threads_displaying_all": "Tout afficher",
"Threads_displaying_following": "Affichage suivant",
"Threads_displaying_unread": "Affichage non lu",
"No_threads": "Il n'y a pas de fils",
"No_threads_following": "Vous ne suivez aucun fil de discussion",
"No_threads_unread": "Il n'y a pas de fils non lus",
"Messagebox_Send_to_channel": "Envoyer au canal",
"Leader": "Leader",
"Moderator": "Modérateur",
"Owner": "Propriétaire",
"Remove_from_room": "Retirer du salon",
"Ignore": "Ignorer",
"Unignore": "Ne pas ignorer",
"User_has_been_ignored": "L'utilisateur a été ignoré",
"User_has_been_unignored": "L'utilisateur n'est plus ignoré",
"User_has_been_removed_from_s": "L'utilisateur a été retiré de {{s}}",
"User__username__is_now_a_leader_of__room_name_": "L'utilisateur {{username}} est désormais un leader de {{room_name}}",
"User__username__is_now_a_moderator_of__room_name_": "L'utilisateur {{username}} est désormais un modérateur de {{room_name}}",
"User__username__is_now_a_owner_of__room_name_": "L'utilisateur {{username}} est désormais un propriétaire de {{room_name}}",
"User__username__removed_from__room_name__leaders": "L'utilisateur {{username}} a été supprimé des leaders de {{room_name}}",
"User__username__removed_from__room_name__moderators": "L'utilisateur {{username}} a été supprimé des modérateurs de {{room_name}}",
"User__username__removed_from__room_name__owners": "L'utilisateur {{username}} a été supprimé des propriétaires de {{room_name}}",
"The_user_will_be_removed_from_s": "L'utilisateur sera supprimé de {{s}}",
"Yes_remove_user": "Oui, supprimez l'utilisateur !",
"Direct_message": "Message direct",
"Message_Ignored": "Message ignoré. Touchez pour l'afficher.",
"Enter_workspace_URL": "Entrez l'URL de l'espace de travail",
"Workspace_URL_Example": "Ex. votre-société.rocket.chat",
"This_room_encryption_has_been_enabled_by__username_": "Le cryptage de ce salon a été activé par {{username}}",
"This_room_encryption_has_been_disabled_by__username_": "Le cryptage de ce salon a été désactivé par {{username}}",
"Teams": "Equipes",
"No_team_channels_found": "Aucun canal trouvé",
"Team_not_found": "Equipe non trouvée",
"Create_Team": "Créer une équipe",
"Team_Name": "Nom de l'équipe",
"Private_Team": "Equipe privée",
"Read_Only_Team": "Equipe en lecture seule",
"Broadcast_Team": "Equipe de diffusion",
"creating_team": "création de l'équipe",
"team-name-already-exists": "Une équipe portant ce nom existe déjà",
"Add_Channel_to_Team": "Ajouter un canal à l'équipe",
"Create_New": "Créer un nouveau",
"Add_Existing": "Ajouter existant",
"Add_Existing_Channel": "Ajouter un canal existant",
"Remove_from_Team": "Retirer de l'équipe",
"Auto-join": "Rejoindre automatiquement",
"Remove_Team_Room_Warning": "Souhaitez-vous supprimer ce canal de l'équipe ? Le canal sera déplacé vers l'espace de travail",
"Confirmation": "Confirmation",
"invalid-room": "Salon invalide",
"You_are_leaving_the_team": "Vous quittez l'équipe '{{team}}'",
"Leave_Team": "Quitter l'équipe",
"Select_Team": "Sélectionnez l'équipe",
"Select_Team_Channels": "Sélectionnez les canaux de l'équipe que vous souhaitez quitter.",
"Cannot_leave": "Ne peut pas partir",
"Cannot_remove": "Impossible d'enlever",
"Cannot_delete": "Impossible de supprimer",
"Last_owner_team_room": "Vous êtes le dernier propriétaire de ce canal. Une fois que vous quittez l'équipe, le canal sera conservé au sein de l'équipe mais vous le gérerez de l'extérieur.",
"last-owner-can-not-be-removed": "Le dernier propriétaire ne peut pas être supprimé",
"Remove_User_Teams": "Sélectionnez les canaux dont vous souhaitez supprimer l'utilisateur.",
"Delete_Team": "Supprimer l'équipe",
"Select_channels_to_delete": "Ceci ne peut pas être annulé. Une fois que vous supprimez une équipe, tout le contenu et la configuration du chat seront supprimés.\n\nSélectionnez les canaux que vous souhaitez supprimer. Ceux que vous décidez de conserver seront disponible dans votre espace de travail. Notez que les canaux publics seront toujours publics et visibles par tous.",
"You_are_deleting_the_team": "Vous supprimez cette équipe.",
"Removing_user_from_this_team": "Vous supprimez {{user}} de cette équipe",
"Remove_User_Team_Channels": "Sélectionnez les canaux dont vous souhaitez supprimer l'utilisateur.",
"Remove_Member": "Supprimer un membre",
"leaving_team": "quitter l'équipe",
"removing_team": "retirer de l'équipe",
"moving_channel_to_team": "transfert de canal à l'équipe",
"deleting_team": "suppression de l'équipe",
"member-does-not-exist": "Le membre n'existe pas",
"Convert": "Convertir",
"Convert_to_Team": "Convertir en équipe",
"Convert_to_Team_Warning": "Ceci ne peut pas être annulé. Une fois que vous avez converti un canal en équipe, vous ne pouvez pas le retransformer en canal.",
"Move_to_Team": "Déplacer vers l'équipe",
"Move_Channel_Paragraph": "Le déplacement d'un canal dans une équipe signifie que ce canal sera ajouté dans le contexte d'équipe. Cependant, tous les membres du canal, qui ne sont pas membres de l'équipe respective, auront toujours accès à ce canal, mais ne seront pas ajoutés comme membres de l'équipe.\n\nLa gestion de tout le canal sera toujours assurée par les propriétaires de ce canal.\n\nLes membres de l'équipe et même les propriétaires de l'équipe, s'ils ne sont pas membres de ce canal, ne peuvent pas avoir accès au contenu du canal.\n\nVeuillez noter que le propriétaire de l'équipe pourra supprimer des membres du canal.",
"Move_to_Team_Warning": "Après avoir lu les instructions précédentes sur ce comportement, voulez-vous toujours déplacer ce canal vers l'équipe sélectionnée ?",
"Load_More": "Charger plus",
"Load_Newer": "Charger plus récent",
"Load_Older": "Charger plus ancien"
} }

View File

@ -33,7 +33,7 @@
"error-invalid-date": "Data fornita non valida.", "error-invalid-date": "Data fornita non valida.",
"error-invalid-description": "Descrizione non valida", "error-invalid-description": "Descrizione non valida",
"error-invalid-domain": "Dominio non valido", "error-invalid-domain": "Dominio non valido",
"error-invalid-email": "E-mail {{emai}} non valida", "error-invalid-email": "E-mail {{email}} non valida",
"error-invalid-email-address": "Indirizzo e-mail non valido", "error-invalid-email-address": "Indirizzo e-mail non valido",
"error-invalid-file-height": "Altezza del file non valida", "error-invalid-file-height": "Altezza del file non valida",
"error-invalid-file-type": "Tipo di file non valido", "error-invalid-file-type": "Tipo di file non valido",
@ -157,8 +157,8 @@
"Continue_with": "Continua con", "Continue_with": "Continua con",
"Copied_to_clipboard": "Copiato negli appunti!", "Copied_to_clipboard": "Copiato negli appunti!",
"Copy": "Copia", "Copy": "Copia",
"Permalink": "Permalink",
"Conversation": "Conversazione", "Conversation": "Conversazione",
"Permalink": "Permalink",
"Certificate_password": "Password certificato", "Certificate_password": "Password certificato",
"Clear_cache": "Cancella la cache locale", "Clear_cache": "Cancella la cache locale",
"Clear_cache_loading": "Cancellando la cache.", "Clear_cache_loading": "Cancellando la cache.",
@ -290,11 +290,11 @@
"last_message": "ultimo messaggio", "last_message": "ultimo messaggio",
"Leave_channel": "Abbandona canale", "Leave_channel": "Abbandona canale",
"leaving_room": "abbandonando stanza", "leaving_room": "abbandonando stanza",
"Leave": "Lasciare il canale",
"leave": "abbandona", "leave": "abbandona",
"Legal": "Informazioni", "Legal": "Informazioni",
"Light": "Chiaro", "Light": "Chiaro",
"License": "Licenza", "License": "Licenza",
"Livechat": "Livechat",
"Livechat_edit": "Modifica Livechat", "Livechat_edit": "Modifica Livechat",
"Login": "Accedi", "Login": "Accedi",
"Login_error": "Le tue credenziali sono state rifiutate! Prova di nuovo.", "Login_error": "Le tue credenziali sono state rifiutate! Prova di nuovo.",
@ -681,12 +681,6 @@
"No_threads_following": "Non stai seguendo alcun thread", "No_threads_following": "Non stai seguendo alcun thread",
"No_threads_unread": "Non ci sono thread non letti", "No_threads_unread": "Non ci sono thread non letti",
"Messagebox_Send_to_channel": "Invia sul canale", "Messagebox_Send_to_channel": "Invia sul canale",
"Set_as_leader": "Rendi leader",
"Set_as_moderator": "Rendi moderatore",
"Set_as_owner": "Rendi proprietario",
"Remove_as_leader": "Rimuovi come leader",
"Remove_as_moderator": "Rimuovi come moderatore",
"Remove_as_owner": "Rimuovi come proprietario",
"Remove_from_room": "Rimuovi dalla stanza", "Remove_from_room": "Rimuovi dalla stanza",
"Ignore": "Ignora", "Ignore": "Ignora",
"Unignore": "Non ignorare", "Unignore": "Non ignorare",
@ -704,5 +698,6 @@
"Direct_message": "Messaggio diretto", "Direct_message": "Messaggio diretto",
"Message_Ignored": "Messaggio ignorato. Tocca per visualizzarlo.", "Message_Ignored": "Messaggio ignorato. Tocca per visualizzarlo.",
"Enter_workspace_URL": "Inserisci la url del workspace", "Enter_workspace_URL": "Inserisci la url del workspace",
"Workspace_URL_Example": "Es. tua-azienda.rocket.chat" "Workspace_URL_Example": "Es. tua-azienda.rocket.chat",
"invalid-room": "Canale non valido"
} }

View File

@ -31,7 +31,7 @@
"error-invalid-date": "不正な日時です", "error-invalid-date": "不正な日時です",
"error-invalid-description": "不正な詳細です", "error-invalid-description": "不正な詳細です",
"error-invalid-domain": "不正なドメインです", "error-invalid-domain": "不正なドメインです",
"error-invalid-email": "不正なメールアドレスです。 {{emai}}", "error-invalid-email": "不正なメールアドレスです。 {{email}}",
"error-invalid-email-address": "不正なメールアドレスです", "error-invalid-email-address": "不正なメールアドレスです",
"error-invalid-file-height": "ファイルの高さが不正です", "error-invalid-file-height": "ファイルの高さが不正です",
"error-invalid-file-type": "ファイルの種類が不正です", "error-invalid-file-type": "ファイルの種類が不正です",
@ -179,7 +179,6 @@
"Email": "メールアドレス", "Email": "メールアドレス",
"email": "メールアドレス", "email": "メールアドレス",
"Enable_Auto_Translate": "自動翻訳を有効にする", "Enable_Auto_Translate": "自動翻訳を有効にする",
"Enable_markdown": "マークダウンを有効にする",
"Enable_notifications": "通知を有効にする", "Enable_notifications": "通知を有効にする",
"Everyone_can_access_this_channel": "全員このチャンネルにアクセスできます", "Everyone_can_access_this_channel": "全員このチャンネルにアクセスできます",
"Error_uploading": "アップロードエラー", "Error_uploading": "アップロードエラー",
@ -220,6 +219,7 @@
"last_message": "最後のメッセージ", "last_message": "最後のメッセージ",
"Leave_channel": "チャンネルを退出", "Leave_channel": "チャンネルを退出",
"leaving_room": "チャンネルを退出", "leaving_room": "チャンネルを退出",
"Leave": "ルームを退出",
"leave": "退出", "leave": "退出",
"Legal": "法的項目", "Legal": "法的項目",
"Light": "ライト", "Light": "ライト",
@ -432,7 +432,7 @@
"Users": "ユーザー", "Users": "ユーザー",
"User_added_by": "{{userBy}} が {{userAdded}} を追加しました", "User_added_by": "{{userBy}} が {{userAdded}} を追加しました",
"User_Info": "ユーザー情報", "User_Info": "ユーザー情報",
"User_has_been_key": "ユーザーは{{ key }}", "User_has_been_key": "ユーザーは{{key}}",
"User_is_no_longer_role_by_": "{{userBy}} は {{user}} のロール {{role}} を削除しました。", "User_is_no_longer_role_by_": "{{userBy}} は {{user}} のロール {{role}} を削除しました。",
"User_muted_by": "{{userBy}} は {{userMuted}} をミュートしました。", "User_muted_by": "{{userBy}} は {{userMuted}} をミュートしました。",
"User_removed_by": "{{userBy}} は {{userRemoved}} を退出させました。", "User_removed_by": "{{userBy}} は {{userRemoved}} を退出させました。",
@ -488,5 +488,6 @@
"New_line": "新しい行", "New_line": "新しい行",
"You_will_be_logged_out_of_this_application": "アプリからログアウトします。", "You_will_be_logged_out_of_this_application": "アプリからログアウトします。",
"Clear": "クリア", "Clear": "クリア",
"This_will_clear_all_your_offline_data": "オフラインデータをすべて削除します。" "This_will_clear_all_your_offline_data": "オフラインデータをすべて削除します。",
"invalid-room": "無効なルーム"
} }

File diff suppressed because it is too large Load Diff

View File

@ -62,27 +62,20 @@
"error-no-tokens-for-this-user": "Não existem tokens para este usuário", "error-no-tokens-for-this-user": "Não existem tokens para este usuário",
"error-not-allowed": "Não permitido", "error-not-allowed": "Não permitido",
"error-not-authorized": "Não autorizado", "error-not-authorized": "Não autorizado",
"error-password-policy-not-met": "A senha não atende a política do servidor",
"error-password-policy-not-met-maxLength": "A senha não está de acordo com a política de comprimento máximo do servidor (senha muito longa)",
"error-password-policy-not-met-minLength": "A senha não está de acordo com a política de comprimento mínimo do servidor (senha muito curta)",
"error-password-policy-not-met-oneLowercase": "A senha não está de acordo com a política do servidor de pelo menos um caractere minúsculo.",
"error-password-policy-not-met-oneNumber": "A senha não está de acordo com a política do servidor, de pelo menos um caractere numérico.",
"error-password-policy-not-met-oneSpecial": "A senha não está de acordo com a política do servidor, de pelo menos um caractere especial.",
"error-password-policy-not-met-oneUppercase": "A senha não está de acordo com a política do servidor, de pelo menos um caractere maiúsculo.",
"error-password-policy-not-met-repeatingCharacters": "A senha não está de acordo com a política do servidor, relativamente aos caracteres proibidos repetidos (existem vários caracteres proibidos próximos uns dos outros)",
"error-push-disabled": "Notificações push desativadas", "error-push-disabled": "Notificações push desativadas",
"error-remove-last-owner": "Este é o último proprietário. Por favor, defina um novo proprietário antes de remover este.", "error-remove-last-owner": "Este é o último proprietário. Por favor, defina um novo proprietário antes de remover este.",
"error-role-in-use": "Não é possível remover o papel pois ele está em uso", "error-role-in-use": "Não é possível remover o papel pois ele está em uso",
"error-role-name-required": "Nome do papel é obrigatório", "error-role-name-required": "Nome do papel é obrigatório",
"error-the-field-is-required": "O campo {{field}} é obrigatório.", "error-the-field-is-required": "O campo {{field}} é obrigatório.",
"error-too-many-requests": "Erro, muitas solicitações. Por favor, diminua a velocidade. Você deve esperar {{seconds}} segundos antes de tentar novamente.", "error-too-many-requests": "Erro, muitas solicitações. Por favor, diminua a velocidade. Você deve esperar {{seconds}} segundos antes de tentar novamente.",
"error-user-has-no-roles": "O usuário não possui permissões",
"error-user-is-not-activated": "O usuário não está ativo", "error-user-is-not-activated": "O usuário não está ativo",
"error-user-has-no-roles": "O usuário não possui permissões",
"error-user-limit-exceeded": "O número de usuários que você está tentando convidar para #channel_name excede o limite determindado pelo administrador", "error-user-limit-exceeded": "O número de usuários que você está tentando convidar para #channel_name excede o limite determindado pelo administrador",
"error-user-not-in-room": "O usuário não está nesta sala", "error-user-not-in-room": "O usuário não está nesta sala",
"error-user-registration-disabled": "O registro do usuário está desativado", "error-user-registration-disabled": "O registro do usuário está desativado",
"error-user-registration-secret": "O registro de usuário é permitido somente via URL secreta", "error-user-registration-secret": "O registro de usuário é permitido somente via URL secreta",
"error-you-are-last-owner": "Você é o último proprietário da sala. Por favor defina um novo proprietário antes de sair.", "error-you-are-last-owner": "Você é o último proprietário da sala. Por favor defina um novo proprietário antes de sair.",
"error-status-not-allowed": "O status invisível está desativado",
"Actions": "Ações", "Actions": "Ações",
"activity": "atividade", "activity": "atividade",
"Activity": "Atividade", "Activity": "Atividade",
@ -102,6 +95,7 @@
"and": "e", "and": "e",
"announcement": "anúncio", "announcement": "anúncio",
"Announcement": "Anúncio", "Announcement": "Anúncio",
"Apply_Your_Certificate": "Aplicar certificado",
"ARCHIVE": "ARQUIVAR", "ARCHIVE": "ARQUIVAR",
"archive": "arquivar", "archive": "arquivar",
"are_typing": "estão digitando", "are_typing": "estão digitando",
@ -131,10 +125,7 @@
"Channel_Name": "Nome do Canal", "Channel_Name": "Nome do Canal",
"Channels": "Canais", "Channels": "Canais",
"Chats": "Conversas", "Chats": "Conversas",
"Change_Language": "Alterar idioma",
"Change_language_loading": "Alterando idioma.",
"Call_already_ended": "A chamada já terminou!", "Call_already_ended": "A chamada já terminou!",
"Clear_cache_loading": "Limpando cache.",
"Clear_cookies_alert": "Você quer limpar seus cookies?", "Clear_cookies_alert": "Você quer limpar seus cookies?",
"Clear_cookies_desc": "Esta ação limpará todos os cookies de login permitindo que você faça login em outras contas.", "Clear_cookies_desc": "Esta ação limpará todos os cookies de login permitindo que você faça login em outras contas.",
"Clear_cookies_yes": "Sim, limpar cookies", "Clear_cookies_yes": "Sim, limpar cookies",
@ -143,8 +134,9 @@
"Close": "Fechar", "Close": "Fechar",
"Close_emoji_selector": "Fechar seletor de emojis", "Close_emoji_selector": "Fechar seletor de emojis",
"Closing_chat": "Fechando conversa", "Closing_chat": "Fechando conversa",
"Choose": "Escolher", "Change_language_loading": "Alterando idioma.",
"Chat_closed_by_agent": "Conversa fechada por agente", "Chat_closed_by_agent": "Conversa fechada por agente",
"Choose": "Escolher",
"Choose_from_library": "Escolha da biblioteca", "Choose_from_library": "Escolha da biblioteca",
"Choose_file": "Enviar arquivo", "Choose_file": "Enviar arquivo",
"Choose_where_you_want_links_be_opened": "Escolha onde deseja que os links sejam abertos", "Choose_where_you_want_links_be_opened": "Escolha onde deseja que os links sejam abertos",
@ -154,15 +146,16 @@
"Confirm": "Confirmar", "Confirm": "Confirmar",
"Connect": "Conectar", "Connect": "Conectar",
"Connected": "Conectado", "Connected": "Conectado",
"Conversation": "Conversação",
"connecting_server": "conectando no servidor", "connecting_server": "conectando no servidor",
"Connecting": "Conectando...", "Connecting": "Conectando...",
"Contact_us": "Entre em contato", "Contact_us": "Entre em contato",
"Continue_with": "Entrar com",
"Contact_your_server_admin": "Contate o administrador do servidor.", "Contact_your_server_admin": "Contate o administrador do servidor.",
"Continue_with": "Entrar com",
"Copied_to_clipboard": "Copiado para a área de transferência!", "Copied_to_clipboard": "Copiado para a área de transferência!",
"Copy": "Copiar", "Copy": "Copiar",
"Conversation": "Conversação",
"Permalink": "Link-Permanente", "Permalink": "Link-Permanente",
"Clear_cache_loading": "Limpando cache.",
"Create_account": "Criar conta", "Create_account": "Criar conta",
"Create_Channel": "Criar Canal", "Create_Channel": "Criar Canal",
"Create_Direct_Messages": "Criar Mensagens Diretas", "Create_Direct_Messages": "Criar Mensagens Diretas",
@ -172,19 +165,21 @@
"Create": "Criar", "Create": "Criar",
"Dark": "Escuro", "Dark": "Escuro",
"Dark_level": "Nível escuro", "Dark_level": "Nível escuro",
"Default": "Padrão",
"Default_browser": "Navegador padrão", "Default_browser": "Navegador padrão",
"Delete_Room_Warning": "A exclusão de uma sala irá apagar todas as mensagens postadas na sala. Isso não pode ser desfeito.", "Delete_Room_Warning": "A exclusão de uma sala irá apagar todas as mensagens postadas na sala. Isso não pode ser desfeito.",
"Department": "Departamento",
"delete": "excluir", "delete": "excluir",
"Delete": "Excluir", "Delete": "Excluir",
"DELETE": "EXCLUIR", "DELETE": "EXCLUIR",
"deleting_room": "excluindo sala", "deleting_room": "excluindo sala",
"Direct_Messages": "Mensagens Diretas", "description": "descrição",
"Description": "Descrição",
"Desktop_Options": "Opções De Área De Trabalho", "Desktop_Options": "Opções De Área De Trabalho",
"Desktop_Notifications": "Notificações da Área de Trabalho", "Desktop_Notifications": "Notificações da Área de Trabalho",
"Desktop_Alert_info": "Essas notificações são entregues a você na área de trabalho", "Desktop_Alert_info": "Essas notificações são entregues a você na área de trabalho",
"Directory": "Diretório", "Directory": "Diretório",
"description": "descrição", "Direct_Messages": "Mensagens Diretas",
"Description": "Descrição",
"Disable_notifications": "Desabilitar notificações", "Disable_notifications": "Desabilitar notificações",
"Discussions": "Discussões", "Discussions": "Discussões",
"Discussion_Desc": "Ajude a manter uma visão geral sobre o que está acontecendo! Ao criar uma discussão, um sub-canal do que você selecionou é criado e os dois são vinculados.", "Discussion_Desc": "Ajude a manter uma visão geral sobre o que está acontecendo! Ao criar uma discussão, um sub-canal do que você selecionou é criado e os dois são vinculados.",
@ -192,6 +187,7 @@
"Done": "Pronto", "Done": "Pronto",
"Dont_Have_An_Account": "Não tem uma conta?", "Dont_Have_An_Account": "Não tem uma conta?",
"Do_you_have_an_account": "Você tem uma conta?", "Do_you_have_an_account": "Você tem uma conta?",
"Do_you_have_a_certificate": "Você tem um certificado?",
"Do_you_really_want_to_key_this_room_question_mark": "Você quer realmente {{key}} esta sala?", "Do_you_really_want_to_key_this_room_question_mark": "Você quer realmente {{key}} esta sala?",
"E2E_Encryption": "Encriptação ponta a ponta", "E2E_Encryption": "Encriptação ponta a ponta",
"E2E_How_It_Works_info1": "Agora você pode criar grupos privados criptografados e mensagens diretas. Você também pode alterar grupos privados existentes ou DMs para criptografados.", "E2E_How_It_Works_info1": "Agora você pode criar grupos privados criptografados e mensagens diretas. Você também pode alterar grupos privados existentes ou DMs para criptografados.",
@ -201,16 +197,16 @@
"edit": "editar", "edit": "editar",
"edited": "editado", "edited": "editado",
"Edit": "Editar", "Edit": "Editar",
"Edit_Invite": "Editar convite",
"Edit_Status": "Editar Status", "Edit_Status": "Editar Status",
"Edit_Invite": "Editar convite",
"End_to_end_encrypted_room": "Sala criptografada de ponta a ponta", "End_to_end_encrypted_room": "Sala criptografada de ponta a ponta",
"end_to_end_encryption": "criptografia de ponta a ponta", "end_to_end_encryption": "criptografia de ponta a ponta",
"Email_Notification_Mode_All": "Cada Menção / Mensagem Direta",
"Email_Notification_Mode_Disabled": "Desativado",
"Email_or_password_field_is_empty": "Email ou senha estão vazios", "Email_or_password_field_is_empty": "Email ou senha estão vazios",
"Email": "E-mail", "Email": "E-mail",
"email": "e-mail", "email": "e-mail",
"Empty_title": "Título vazio", "Empty_title": "Título vazio",
"Email_Notification_Mode_All": "Cada Menção / Mensagem Direta",
"Email_Notification_Mode_Disabled": "Desativado",
"Enable_Auto_Translate": "Ativar a tradução automática", "Enable_Auto_Translate": "Ativar a tradução automática",
"Enable_notifications": "Habilitar notificações", "Enable_notifications": "Habilitar notificações",
"Encrypted": "Criptografado", "Encrypted": "Criptografado",
@ -223,6 +219,7 @@
"Everyone_can_access_this_channel": "Todos podem acessar este canal", "Everyone_can_access_this_channel": "Todos podem acessar este canal",
"Error_uploading": "Erro subindo", "Error_uploading": "Erro subindo",
"Expiration_Days": "Expira em (dias)", "Expiration_Days": "Expira em (dias)",
"Favorite": "Adicionar aos Favoritos",
"Favorites": "Favoritos", "Favorites": "Favoritos",
"Files": "Arquivos", "Files": "Arquivos",
"File_description": "Descrição do arquivo", "File_description": "Descrição do arquivo",
@ -241,6 +238,7 @@
"Generate_New_Link": "Gerar novo convite", "Generate_New_Link": "Gerar novo convite",
"Group_by_favorites": "Agrupar favoritos", "Group_by_favorites": "Agrupar favoritos",
"Group_by_type": "Agrupar por tipo", "Group_by_type": "Agrupar por tipo",
"Hide": "Ocultar",
"Has_joined_the_channel": "entrou no canal", "Has_joined_the_channel": "entrou no canal",
"Has_joined_the_conversation": "entrou na conversa", "Has_joined_the_conversation": "entrou na conversa",
"Has_left_the_channel": "saiu da conversa", "Has_left_the_channel": "saiu da conversa",
@ -279,6 +277,7 @@
"last_message": "última mensagem", "last_message": "última mensagem",
"Leave_channel": "Sair do canal", "Leave_channel": "Sair do canal",
"leaving_room": "saindo do canal", "leaving_room": "saindo do canal",
"Leave": "Sair da sala",
"leave": "sair", "leave": "sair",
"Legal": "Legal", "Legal": "Legal",
"Light": "Claro", "Light": "Claro",
@ -286,8 +285,8 @@
"Login": "Entrar", "Login": "Entrar",
"Login_error": "Suas credenciais foram rejeitadas. Tente novamente por favor!", "Login_error": "Suas credenciais foram rejeitadas. Tente novamente por favor!",
"Login_with": "Login with", "Login_with": "Login with",
"Logout": "Sair",
"Logging_out": "Saindo.", "Logging_out": "Saindo.",
"Logout": "Sair",
"Max_number_of_uses": "Número máximo de usos", "Max_number_of_uses": "Número máximo de usos",
"Max_number_of_users_allowed_is_number": "Número máximo de usuários é {{maxUsers}}", "Max_number_of_users_allowed_is_number": "Número máximo de usuários é {{maxUsers}}",
"Members": "Membros", "Members": "Membros",
@ -300,6 +299,7 @@
"Message_removed": "Mensagem removida", "Message_removed": "Mensagem removida",
"message": "mensagem", "message": "mensagem",
"messages": "mensagens", "messages": "mensagens",
"Message": "Mensagem",
"Messages": "Mensagens", "Messages": "Mensagens",
"Microphone_Permission_Message": "Rocket.Chat precisa de acesso ao seu microfone para enviar mensagens de áudio.", "Microphone_Permission_Message": "Rocket.Chat precisa de acesso ao seu microfone para enviar mensagens de áudio.",
"Microphone_Permission": "Acesso ao Microfone", "Microphone_Permission": "Acesso ao Microfone",
@ -311,7 +311,6 @@
"Name": "Nome", "Name": "Nome",
"Navigation_history": "Histórico de navegação", "Navigation_history": "Histórico de navegação",
"Never": "Nunca", "Never": "Nunca",
"New_in_RocketChat_question_mark": "Novo no Rocket.Chat?",
"New_Message": "Nova Mensagem", "New_Message": "Nova Mensagem",
"New_Password": "Nova Senha", "New_Password": "Nova Senha",
"Next": "Próximo", "Next": "Próximo",
@ -326,19 +325,20 @@
"No_Message": "Não há mensagens", "No_Message": "Não há mensagens",
"No_messages_yet": "Não há mensagens ainda", "No_messages_yet": "Não há mensagens ainda",
"No_Reactions": "Sem reações", "No_Reactions": "Sem reações",
"Not_RC_Server": "Este não é um servidor Rocket.Chat.\n{{contact}}",
"Nothing": "Nada",
"Nothing_to_save": "Nada para salvar!", "Nothing_to_save": "Nada para salvar!",
"Notify_active_in_this_room": "Notificar usuários ativos nesta sala", "Notify_active_in_this_room": "Notificar usuários ativos nesta sala",
"Notify_all_in_this_room": "Notificar todos nesta sala", "Notify_all_in_this_room": "Notificar todos nesta sala",
"Notifications": "Notificações", "Notifications": "Notificações",
"Notification_Duration": "Duração da notificação", "Notification_Duration": "Duração da notificação",
"Notification_Preferences": "Preferências de notificação", "Notification_Preferences": "Preferências de notificação",
"Not_RC_Server": "Este não é um servidor Rocket.Chat.\n{{contact}}",
"No_available_agents_to_transfer": "Nenhum agente disponível para transferência", "No_available_agents_to_transfer": "Nenhum agente disponível para transferência",
"Offline": "Offline", "Offline": "Offline",
"Oops": "Ops!",
"Omnichannel": "Omnichannel", "Omnichannel": "Omnichannel",
"Open_Livechats": "Bate-papos em Andamento", "Open_Livechats": "Bate-papos em Andamento",
"Omnichannel_enable_alert": "Você não está disponível no Omnichannel. Você quer ficar disponível?", "Omnichannel_enable_alert": "Você não está disponível no Omnichannel. Você quer ficar disponível?",
"Oops": "Ops!",
"Onboarding_description": "Workspace é o espaço de colaboração do seu time ou organização. Peça um convite ou o endereço ao seu administrador ou crie uma workspace para o seu time.", "Onboarding_description": "Workspace é o espaço de colaboração do seu time ou organização. Peça um convite ou o endereço ao seu administrador ou crie uma workspace para o seu time.",
"Onboarding_join_workspace": "Entre numa workspace", "Onboarding_join_workspace": "Entre numa workspace",
"Onboarding_subtitle": "Além da colaboração em equipe", "Onboarding_subtitle": "Além da colaboração em equipe",
@ -358,13 +358,14 @@
"Password": "Senha", "Password": "Senha",
"Parent_channel_or_group": "Canal ou grupo pai", "Parent_channel_or_group": "Canal ou grupo pai",
"Permalink_copied_to_clipboard": "Link-permanente copiado para a área de transferência!", "Permalink_copied_to_clipboard": "Link-permanente copiado para a área de transferência!",
"Phone": "Telefone",
"Pin": "Fixar", "Pin": "Fixar",
"Pinned_Messages": "Mensagens Fixadas", "Pinned_Messages": "Mensagens Fixadas",
"pinned": "fixada", "pinned": "fixada",
"Pinned": "Mensagens Fixadas", "Pinned": "Mensagens Fixadas",
"Please_wait": "Por favor, aguarde.",
"Please_enter_your_password": "Por favor, digite sua senha",
"Please_add_a_comment": "Por favor, adicione um comentário", "Please_add_a_comment": "Por favor, adicione um comentário",
"Please_enter_your_password": "Por favor, digite sua senha",
"Please_wait": "Por favor, aguarde.",
"Preferences": "Preferências", "Preferences": "Preferências",
"Preferences_saved": "Preferências salvas!", "Preferences_saved": "Preferências salvas!",
"Privacy_Policy": " Política de Privacidade", "Privacy_Policy": " Política de Privacidade",
@ -386,15 +387,16 @@
"Read_External_Permission": "Permissão de acesso à arquivos", "Read_External_Permission": "Permissão de acesso à arquivos",
"Read_Only_Channel": "Canal Somente Leitura", "Read_Only_Channel": "Canal Somente Leitura",
"Read_Only": "Somente Leitura", "Read_Only": "Somente Leitura",
"Read_Receipt": "Lida por",
"Receive_Group_Mentions": "Receber menções de grupo", "Receive_Group_Mentions": "Receber menções de grupo",
"Receive_Group_Mentions_Info": "Receber menções @all e @here", "Receive_Group_Mentions_Info": "Receber menções @all e @here",
"Register": "Registrar", "Register": "Registrar",
"Read_Receipt": "Lida por",
"Repeat_Password": "Repetir Senha", "Repeat_Password": "Repetir Senha",
"Replied_on": "Respondido em:", "Replied_on": "Respondido em:",
"replies": "respostas", "replies": "respostas",
"reply": "resposta", "reply": "resposta",
"Reply": "Responder", "Reply": "Responder",
"Report": "Reportar",
"Receive_Notification": "Receber Notificação", "Receive_Notification": "Receber Notificação",
"Receive_notifications_from": "Receber notificação de {{name}}", "Receive_notifications_from": "Receber notificação de {{name}}",
"Resend": "Reenviar", "Resend": "Reenviar",
@ -424,6 +426,7 @@
"SAVE": "SALVAR", "SAVE": "SALVAR",
"Save_Changes": "Salvar Alterações", "Save_Changes": "Salvar Alterações",
"Save": "Salvar", "Save": "Salvar",
"Saved": "Salvo",
"saving_preferences": "salvando preferências", "saving_preferences": "salvando preferências",
"saving_profile": "salvando perfil", "saving_profile": "salvando perfil",
"saving_settings": "salvando configurações", "saving_settings": "salvando configurações",
@ -472,12 +475,14 @@
"starred": "favoritou", "starred": "favoritou",
"Starred": "Mensagens Favoritas", "Starred": "Mensagens Favoritas",
"Start_of_conversation": "Início da conversa", "Start_of_conversation": "Início da conversa",
"Started_call": "Chamada iniciada por {{userBy}}", "Start_a_Discussion": "Iniciar uma Discussão",
"Started_discussion": "Iniciou uma discussão:", "Started_discussion": "Iniciou uma discussão:",
"Started_call": "Chamada iniciada por {{userBy}}",
"Submit": "Enviar", "Submit": "Enviar",
"Table": "Tabela", "Table": "Tabela",
"Take_a_photo": "Tirar uma foto", "Take_a_photo": "Tirar uma foto",
"Take_a_video": "Gravar um vídeo", "Take_a_video": "Gravar um vídeo",
"Take_it": "Pegue!",
"Terms_of_Service": " Termos de Serviço ", "Terms_of_Service": " Termos de Serviço ",
"Theme": "Tema", "Theme": "Tema",
"The_user_wont_be_able_to_type_in_roomName": "O usuário não poderá digitar em {{roomName}}", "The_user_wont_be_able_to_type_in_roomName": "O usuário não poderá digitar em {{roomName}}",
@ -491,12 +496,14 @@
"To": "Para", "To": "Para",
"topic": "tópico", "topic": "tópico",
"Topic": "Tópico", "Topic": "Tópico",
"Translate": "Traduzir",
"Try_again": "Tentar novamente", "Try_again": "Tentar novamente",
"Two_Factor_Authentication": "Autenticação de dois fatores", "Two_Factor_Authentication": "Autenticação de dois fatores",
"Type_the_channel_name_here": "Digite o nome do canal", "Type_the_channel_name_here": "Digite o nome do canal",
"unarchive": "desarquivar", "unarchive": "desarquivar",
"UNARCHIVE": "DESARQUIVAR", "UNARCHIVE": "DESARQUIVAR",
"Unblock_user": "Desbloquear usuário", "Unblock_user": "Desbloquear usuário",
"Unfavorite": "Remover dos Favoritos",
"Unfollowed_thread": "Parou de seguir tópico", "Unfollowed_thread": "Parou de seguir tópico",
"Unmute": "Permitir que o usuário fale", "Unmute": "Permitir que o usuário fale",
"unmuted": "permitiu que o usuário fale", "unmuted": "permitiu que o usuário fale",
@ -511,6 +518,7 @@
"User": "Usuário", "User": "Usuário",
"Users": "Usuários", "Users": "Usuários",
"User_added_by": "Usuário {{userAdded}} adicionado por {{userBy}}", "User_added_by": "Usuário {{userAdded}} adicionado por {{userBy}}",
"User_Info": "Informações do usuário",
"User_has_been_key": "Usuário foi {{key}}", "User_has_been_key": "Usuário foi {{key}}",
"User_is_no_longer_role_by_": "{{user}} não pertence mais à {{role}} por {{userBy}}", "User_is_no_longer_role_by_": "{{user}} não pertence mais à {{role}} por {{userBy}}",
"User_muted_by": "User {{userMuted}} muted por {{userBy}}", "User_muted_by": "User {{userMuted}} muted por {{userBy}}",
@ -527,25 +535,31 @@
"Verify_email_desc": "Nós lhe enviamos um e-mail para confirmar o seu registro. Se você não receber um e-mail em breve, por favor retorne e tente novamente.", "Verify_email_desc": "Nós lhe enviamos um e-mail para confirmar o seu registro. Se você não receber um e-mail em breve, por favor retorne e tente novamente.",
"Verify_your_email_for_the_code_we_sent": "Verifique em seu e-mail o código que enviamos", "Verify_your_email_for_the_code_we_sent": "Verifique em seu e-mail o código que enviamos",
"Video_call": "Chamada de vídeo", "Video_call": "Chamada de vídeo",
"View_Original": "Visualizar original",
"Voice_call": "Chamada de voz", "Voice_call": "Chamada de voz",
"Waiting_for_network": "Aguardando rede...", "Waiting_for_network": "Aguardando rede...",
"Websocket_disabled": "Websocket está desativado para esse servidor.\n{{contact}}", "Websocket_disabled": "Websocket está desativado para esse servidor.\n{{contact}}",
"Welcome": "Bem vindo", "Welcome": "Bem vindo",
"Whats_your_2fa": "Qual seu código de autenticação?",
"What_are_you_doing_right_now": "O que você está fazendo agora?", "What_are_you_doing_right_now": "O que você está fazendo agora?",
"Whats_your_2fa": "Qual seu código de autenticação?",
"Without_Servers": "Sem Servidores", "Without_Servers": "Sem Servidores",
"Workspaces": "Workspaces", "Workspaces": "Workspaces",
"Would_you_like_to_return_the_inquiry": "Deseja retornar a consulta?",
"Write_External_Permission_Message": "Rocket.Chat precisa de acesso à sua galeria para salvar imagens",
"Write_External_Permission": "Acesso à Galeria",
"Yes": "Sim",
"Yes_action_it": "Sim, {{action}}!", "Yes_action_it": "Sim, {{action}}!",
"Yesterday": "Ontem", "Yesterday": "Ontem",
"You_are_in_preview_mode": "Está é uma prévia do canal", "You_are_in_preview_mode": "Está é uma prévia do canal",
"You_are_offline": "Você está offline", "You_are_offline": "Você está offline",
"You_can_search_using_RegExp_eg": "Você pode usar expressões regulares, por exemplo `/^text$/i`", "You_can_search_using_RegExp_eg": "Você pode usar expressões regulares, por exemplo `/^text$/i`",
"You_need_to_verifiy_your_email_address_to_get_notications": "Você precisa confirmar seu endereço de e-mail para obter notificações",
"You_colon": "Você: ", "You_colon": "Você: ",
"you_were_mentioned": "você foi mencionado", "you_were_mentioned": "você foi mencionado",
"You_were_removed_from_channel": "Você foi removido de {{channel}}", "You_were_removed_from_channel": "Você foi removido de {{channel}}",
"you": "você", "you": "você",
"You": "Você", "You": "Você",
"You_need_to_verifiy_your_email_address_to_get_notications": "Você precisa confirmar seu endereço de e-mail para obter notificações",
"Your_certificate": "Seu certificado",
"Your_invite_link_will_expire_after__usesLeft__uses": "Seu link de convite irá vencer depois de {{usesLeft}} usos.", "Your_invite_link_will_expire_after__usesLeft__uses": "Seu link de convite irá vencer depois de {{usesLeft}} usos.",
"Your_invite_link_will_expire_on__date__or_after__usesLeft__uses": "Seu link de convite irá vencer em {{date}} ou depois de {{usesLeft}} usos.", "Your_invite_link_will_expire_on__date__or_after__usesLeft__uses": "Seu link de convite irá vencer em {{date}} ou depois de {{usesLeft}} usos.",
"Your_invite_link_will_expire_on__date__": "Seu link de convite irá vencer em {{date}}.", "Your_invite_link_will_expire_on__date__": "Seu link de convite irá vencer em {{date}}.",
@ -553,10 +567,7 @@
"Your_workspace": "Sua workspace", "Your_workspace": "Sua workspace",
"You_will_not_be_able_to_recover_this_message": "Você não será capaz de recuperar essa mensagem!", "You_will_not_be_able_to_recover_this_message": "Você não será capaz de recuperar essa mensagem!",
"You_will_unset_a_certificate_for_this_server": "Você cancelará a configuração de um certificado para este servidor", "You_will_unset_a_certificate_for_this_server": "Você cancelará a configuração de um certificado para este servidor",
"Would_you_like_to_return_the_inquiry": "Deseja retornar a consulta?", "Change_Language": "Alterar idioma",
"Write_External_Permission_Message": "Rocket.Chat precisa de acesso à sua galeria para salvar imagens",
"Write_External_Permission": "Acesso à Galeria",
"Yes": "Sim",
"Crash_report_disclaimer": "Nós não rastreamos o conteúdo das suas conversas. O relatório de erros e os eventos do analytics apenas contém informações relevantes para identificarmos problemas e corrigí-los.", "Crash_report_disclaimer": "Nós não rastreamos o conteúdo das suas conversas. O relatório de erros e os eventos do analytics apenas contém informações relevantes para identificarmos problemas e corrigí-los.",
"Type_message": "Digitar mensagem", "Type_message": "Digitar mensagem",
"Room_search": "Busca de sala", "Room_search": "Busca de sala",
@ -568,6 +579,7 @@
"Search_messages": "Buscar mensagens", "Search_messages": "Buscar mensagens",
"Scroll_messages": "Rolar mensagens", "Scroll_messages": "Rolar mensagens",
"Reply_latest": "Responder para última mensagem", "Reply_latest": "Responder para última mensagem",
"Reply_in_Thread": "Responder por Tópico",
"Server_selection": "Seleção de servidor", "Server_selection": "Seleção de servidor",
"Server_selection_numbers": "Selecionar servidor 1...9", "Server_selection_numbers": "Selecionar servidor 1...9",
"Add_server": "Adicionar servidor", "Add_server": "Adicionar servidor",
@ -628,12 +640,6 @@
"No_threads_following": "Você não está seguindo tópicos", "No_threads_following": "Você não está seguindo tópicos",
"No_threads_unread": "Não há tópicos não lidos", "No_threads_unread": "Não há tópicos não lidos",
"Messagebox_Send_to_channel": "Mostrar no canal", "Messagebox_Send_to_channel": "Mostrar no canal",
"Set_as_leader": "Definir como líder",
"Set_as_moderator": "Definir como moderador",
"Set_as_owner": "Definir como proprietário",
"Remove_as_leader": "Remover como líder",
"Remove_as_moderator": "Remover como moderador",
"Remove_as_owner": "Remover como owner",
"Remove_from_room": "Remover do canal", "Remove_from_room": "Remover do canal",
"Ignore": "Ignorar", "Ignore": "Ignorar",
"Unignore": "Deixar de ignorar", "Unignore": "Deixar de ignorar",
@ -654,10 +660,10 @@
"Workspace_URL_Example": "Ex. sua-empresa.rocket.chat", "Workspace_URL_Example": "Ex. sua-empresa.rocket.chat",
"This_room_encryption_has_been_enabled_by__username_": "A criptografia para essa sala foi habilitada por {{username}}", "This_room_encryption_has_been_enabled_by__username_": "A criptografia para essa sala foi habilitada por {{username}}",
"This_room_encryption_has_been_disabled_by__username_": "A criptografia para essa sala foi desabilitada por {{username}}", "This_room_encryption_has_been_disabled_by__username_": "A criptografia para essa sala foi desabilitada por {{username}}",
"Apply_Your_Certificate": "Aplicar certificado",
"Do_you_have_a_certificate": "Você tem um certificado?",
"Your_certificate": "Seu certificado",
"Teams": "Times", "Teams": "Times",
"No_team_channels_found": "Nenhum canal encontrado", "No_team_channels_found": "Nenhum canal encontrado",
"Team_not_found": "Time não encontrado" "Team_not_found": "Time não encontrado",
"Private_Team": "Equipe Privada",
"Add_Existing_Channel": "Adicionar Canal Existente",
"invalid-room": "Sala inválida"
} }

View File

@ -30,7 +30,7 @@
"error-invalid-date": "Data inválida fornecida.", "error-invalid-date": "Data inválida fornecida.",
"error-invalid-description": "Descrição inválida", "error-invalid-description": "Descrição inválida",
"error-invalid-domain": "Domínio inválido", "error-invalid-domain": "Domínio inválido",
"error-invalid-email": "E-mail inválido {{emai}}", "error-invalid-email": "E-mail inválido {{email}}",
"error-invalid-email-address": "Endereço de e-mail invalido", "error-invalid-email-address": "Endereço de e-mail invalido",
"error-invalid-file-height": "Altura de ficheiro inválida", "error-invalid-file-height": "Altura de ficheiro inválida",
"error-invalid-file-type": "Tipo de ficheiro inválido", "error-invalid-file-type": "Tipo de ficheiro inválido",
@ -137,14 +137,14 @@
"delete": "apagar", "delete": "apagar",
"Delete": "Apagar", "Delete": "Apagar",
"DELETE": "APAGAR", "DELETE": "APAGAR",
"deleting_room": "apagando sala",
"description": "descrição", "description": "descrição",
"Description": "Descrição", "Description": "Descrição",
"Disable_notifications": "Desactivar notificações",
"Direct_Messages": "Mensagens Directas", "Direct_Messages": "Mensagens Directas",
"Disable_notifications": "Desactivar notificações",
"Dont_Have_An_Account": "Não tem uma conta?", "Dont_Have_An_Account": "Não tem uma conta?",
"Do_you_really_want_to_key_this_room_question_mark": "Você quer mesmo {{key}} esta sala?", "Do_you_really_want_to_key_this_room_question_mark": "Você quer mesmo {{key}} esta sala?",
"edit": "editar", "edit": "editar",
"deleting_room": "apagando sala",
"Edit": "Editar", "Edit": "Editar",
"Email_or_password_field_is_empty": "O campo de e-mail ou palavra-passe está vazio", "Email_or_password_field_is_empty": "O campo de e-mail ou palavra-passe está vazio",
"Email": "E-mail", "Email": "E-mail",

View File

@ -29,7 +29,7 @@
"error-invalid-channel": "Недействительный канал.", "error-invalid-channel": "Недействительный канал.",
"error-invalid-channel-start-with-chars": "Недействительный канал. Начните с @ или #", "error-invalid-channel-start-with-chars": "Недействительный канал. Начните с @ или #",
"error-invalid-custom-field": "Неверное настраиваемое поле", "error-invalid-custom-field": "Неверное настраиваемое поле",
"error-invalid-custom-field-name": "Неверное имя настраиваемого поля. Используйте только буквы, цифры, дефисы и символы подчеркивания.", "error-invalid-custom-field-name": "Неверное имя настраиваемого поля. Используйте только буквы, цифры, дефис и символ подчеркивания.",
"error-invalid-date": "Указана недопустимая дата.", "error-invalid-date": "Указана недопустимая дата.",
"error-invalid-description": "Недопустимое описание", "error-invalid-description": "Недопустимое описание",
"error-invalid-domain": "Недопустимый домен", "error-invalid-domain": "Недопустимый домен",
@ -46,9 +46,9 @@
"error-invalid-password": "Неверный пароль", "error-invalid-password": "Неверный пароль",
"error-invalid-redirectUri": "Недопустимый redirectUri", "error-invalid-redirectUri": "Недопустимый redirectUri",
"error-invalid-role": "Недопустимая роль", "error-invalid-role": "Недопустимая роль",
"error-invalid-room": "Недопустимый канал", "error-invalid-room": "Недопустимый чат",
"error-invalid-room-name": "{{room_name}} не является допустимым именем канала", "error-invalid-room-name": "{{room_name}} не является допустимым именем чата",
"error-invalid-room-type": "{{type}} не является допустимым типом канала.", "error-invalid-room-type": "{{type}} не является допустимым типом чата.",
"error-invalid-settings": "Недопустимые параметры", "error-invalid-settings": "Недопустимые параметры",
"error-invalid-subscription": "Недействительная подписка", "error-invalid-subscription": "Недействительная подписка",
"error-invalid-token": "Недопустимый токен", "error-invalid-token": "Недопустимый токен",
@ -61,6 +61,7 @@
"error-message-editing-blocked": "Правка сообщений заблокирована", "error-message-editing-blocked": "Правка сообщений заблокирована",
"error-message-size-exceeded": "Размер сообщения превышает максимально разрешенный", "error-message-size-exceeded": "Размер сообщения превышает максимально разрешенный",
"error-missing-unsubscribe-link": "Вы должны указать ссылку [отписаться].", "error-missing-unsubscribe-link": "Вы должны указать ссылку [отписаться].",
"error-no-owner-channel": "Вы не являетесь владельцем данного чата",
"error-no-tokens-for-this-user": "Для этого пользователя нет токенов", "error-no-tokens-for-this-user": "Для этого пользователя нет токенов",
"error-not-allowed": "Не допускается", "error-not-allowed": "Не допускается",
"error-not-authorized": "Не разрешено", "error-not-authorized": "Не разрешено",
@ -77,7 +78,8 @@
"error-user-registration-custom-field": "error-user-registration-custom-field", "error-user-registration-custom-field": "error-user-registration-custom-field",
"error-user-registration-disabled": "Регистрация пользователей отключена", "error-user-registration-disabled": "Регистрация пользователей отключена",
"error-user-registration-secret": "Регистрация пользователей разрешена только через секретный URL", "error-user-registration-secret": "Регистрация пользователей разрешена только через секретный URL",
"error-you-are-last-owner": "Вы последний владелец. Пожалуйста, установите нового владельца, прежде чем покинуть комнату.", "error-you-are-last-owner": "Вы последний владелец. Пожалуйста, назначьте нового владельца, прежде чем покинуть чат.",
"error-status-not-allowed": "Статус Невидимый отключён",
"Actions": "Действия", "Actions": "Действия",
"activity": "активности", "activity": "активности",
"Activity": "По активности", "Activity": "По активности",
@ -90,6 +92,7 @@
"alert": "оповещение", "alert": "оповещение",
"alerts": "оповещения", "alerts": "оповещения",
"All_users_in_the_channel_can_write_new_messages": "Все пользователи канала могут писать новые сообщения", "All_users_in_the_channel_can_write_new_messages": "Все пользователи канала могут писать новые сообщения",
"All_users_in_the_team_can_write_new_messages": "Все пользователи в Команде могут писать новые сообщения",
"A_meaningful_name_for_the_discussion_room": "Осмысленное имя для обсуждения", "A_meaningful_name_for_the_discussion_room": "Осмысленное имя для обсуждения",
"All": "Все", "All": "Все",
"All_Messages": "Все сообщения", "All_Messages": "Все сообщения",
@ -180,6 +183,7 @@
"delete": "удалить", "delete": "удалить",
"Delete": "Удалить", "Delete": "Удалить",
"DELETE": "УДАЛИТЬ", "DELETE": "УДАЛИТЬ",
"move": "переместить",
"deleting_room": "удаление чата", "deleting_room": "удаление чата",
"description": "описание", "description": "описание",
"Description": "Описание", "Description": "Описание",
@ -225,6 +229,7 @@
"Encryption_error_title": "Введен не верный пароль шифрования", "Encryption_error_title": "Введен не верный пароль шифрования",
"Encryption_error_desc": "Невозможно расшифровать ваш ключ шифрования, чтобы импортировать его", "Encryption_error_desc": "Невозможно расшифровать ваш ключ шифрования, чтобы импортировать его",
"Everyone_can_access_this_channel": "Каждый может получить доступ к этому каналу", "Everyone_can_access_this_channel": "Каждый может получить доступ к этому каналу",
"Everyone_can_access_this_team": "Каждый может получить доступ к этой Команде",
"Error_uploading": "Ошибка загрузки", "Error_uploading": "Ошибка загрузки",
"Expiration_Days": "Срок действия (Дни)", "Expiration_Days": "Срок действия (Дни)",
"Favorite": "Избранное", "Favorite": "Избранное",
@ -281,13 +286,17 @@
"Invite_Link": "Ссылка Приглашения", "Invite_Link": "Ссылка Приглашения",
"Invite_users": "Приглашение пользователей", "Invite_users": "Приглашение пользователей",
"Join": "Присоединиться", "Join": "Присоединиться",
"Join_Code": "Код присоединения",
"Insert_Join_Code": "Вставить код присоединения",
"Join_our_open_workspace": "Присоединиться к нашему открытому серверу", "Join_our_open_workspace": "Присоединиться к нашему открытому серверу",
"Join_your_workspace": "Присоединиться к вашему серверу", "Join_your_workspace": "Присоединиться к вашему серверу",
"Just_invited_people_can_access_this_channel": "Только приглашенные люди могут получить доступ к этому каналу", "Just_invited_people_can_access_this_channel": "Только приглашенные люди могут получить доступ к этому каналу",
"Just_invited_people_can_access_this_team": "Только приглашенные пользователи могут получить доступ к этой Команде",
"Language": "Язык", "Language": "Язык",
"last_message": "последнее сообщение", "last_message": "последнее сообщение",
"Leave_channel": "Покинуть канал", "Leave_channel": "Покинуть канал",
"leaving_room": "покинуть комнату", "leaving_room": "покинуть комнату",
"Leave": "Покинуть комнату",
"leave": "покинуть", "leave": "покинуть",
"Legal": "Правовые аспекты", "Legal": "Правовые аспекты",
"Light": "Светлая", "Light": "Светлая",
@ -322,8 +331,9 @@
"Mute": "Заглушить", "Mute": "Заглушить",
"muted": "Заглушен", "muted": "Заглушен",
"My_servers": "Мои серверы", "My_servers": "Мои серверы",
"N_person_reacted": "{{n}} людей отреагировало", "N_people_reacted": "отреагировало {{n}} человек",
"N_users": "{{n}} пользователи", "N_users": "{{n}} пользователи",
"N_channels": "{{n}} каналов",
"name": "имя", "name": "имя",
"Name": "Имя", "Name": "Имя",
"Navigation_history": "История навигации", "Navigation_history": "История навигации",
@ -433,6 +443,7 @@
"Review_app_unable_store": "Невозможно открыть {{store}}", "Review_app_unable_store": "Невозможно открыть {{store}}",
"Review_this_app": "Оценить это приложение", "Review_this_app": "Оценить это приложение",
"Remove": "Удалить", "Remove": "Удалить",
"remove": "удалить",
"Roles": "Роли", "Roles": "Роли",
"Room_actions": "Действия с чатом", "Room_actions": "Действия с чатом",
"Room_changed_announcement": "Объявление чата было изменено на: {{announcement}} пользователем {{userBy}}", "Room_changed_announcement": "Объявление чата было изменено на: {{announcement}} пользователем {{userBy}}",
@ -679,12 +690,9 @@
"No_threads_following": "Нет тредов, за которыми вы следите", "No_threads_following": "Нет тредов, за которыми вы следите",
"No_threads_unread": "Непрочитанных тредов нет", "No_threads_unread": "Непрочитанных тредов нет",
"Messagebox_Send_to_channel": "Отправить в чат", "Messagebox_Send_to_channel": "Отправить в чат",
"Set_as_leader": "Назначить лидером", "Leader": "Лидер",
"Set_as_moderator": "Назначить модератором", "Moderator": "Модератор",
"Set_as_owner": "Назначить владельцем", "Owner": "Владелец",
"Remove_as_leader": "Удалить из лидеров",
"Remove_as_moderator": "Удалить из модераторов",
"Remove_as_owner": "Удалить из владельцев",
"Remove_from_room": "Удалить из чата", "Remove_from_room": "Удалить из чата",
"Ignore": "Игнориновать", "Ignore": "Игнориновать",
"Unignore": "Прекратить игнорировать", "Unignore": "Прекратить игнорировать",
@ -704,5 +712,54 @@
"Enter_workspace_URL": "Введите URL вашего рабочего пространства", "Enter_workspace_URL": "Введите URL вашего рабочего пространства",
"Workspace_URL_Example": "Например, your-company.rocket.chat", "Workspace_URL_Example": "Например, your-company.rocket.chat",
"This_room_encryption_has_been_enabled_by__username_": "Шифрование для этого чата включено {{username}}", "This_room_encryption_has_been_enabled_by__username_": "Шифрование для этого чата включено {{username}}",
"This_room_encryption_has_been_disabled_by__username_": "Шифрование для этого чата выключено {{username}}" "This_room_encryption_has_been_disabled_by__username_": "Шифрование для этого чата выключено {{username}}",
"Teams": "Команды",
"No_team_channels_found": "Каналы не найдены",
"Team_not_found": "Команда не найдена",
"Create_Team": "Создать Команду",
"Team_Name": "Имя Команды",
"Private_Team": "Приватная Команда",
"Read_Only_Team": "Команда только для чтения",
"Broadcast_Team": "Широковещательная Команда",
"creating_team": "создание Команды",
"team-name-already-exists": "Команда с таким названием уже существует",
"Add_Channel_to_Team": "Добавить канал в Команду",
"Create_New": "Создать",
"Add_Existing": "Добавить существующее",
"Add_Existing_Channel": "Добавить существующий канал",
"Remove_from_Team": "Удалить из Команды",
"Auto-join": "Автодобавление",
"Remove_Team_Room_Warning": "Хотите ли вы удалить этот канал из Команды? Канал будет перемещен обратно в рабочее пространство",
"Confirmation": "Подтверждение",
"invalid-room": "Такого канала не существует",
"You_are_leaving_the_team": "Вы покидаете Команду '{{team}}'",
"Leave_Team": "Покинуть команду",
"Select_Team": "Выберите Команду",
"Select_Team_Channels": "Выберите каналы Команды, которые вы хотите покинуть.",
"Cannot_leave": "Невозможно выйти",
"Cannot_remove": "Невозможно удалить",
"Cannot_delete": "Невозможно удалить",
"Last_owner_team_room": "Вы последний владелец этого чата. Как только вы покинете Команду, чат будет храниться внутри нее, но вы будете управлять ею снаружи.",
"last-owner-can-not-be-removed": "Последний владелец не может быть удален",
"Remove_User_Teams": "Выберите каналы, из которых вы хотите удалить пользователя.",
"Delete_Team": "Удалить Команду",
"Select_channels_to_delete": "Это нельзя отменить. После удаления Команды все содержимое чата и конфигурация будут удалены \n\nВыберите каналы, которые вы хотите удалить. Те, которые вы решите оставить, будут доступны в вашем рабочем пространстве. Обратите внимание, что публичные каналы по-прежнему будут открытыми и видимыми для всех.",
"You_are_deleting_the_team": "Вы удаляете эту Команду.",
"Removing_user_from_this_team": "Вы удаляете {{user}} из этой Команды",
"Remove_User_Team_Channels": "Выберите каналы, из которых вы хотите удалить пользователя.",
"Remove_Member": "Удалить участника",
"leaving_team": "выход из Команды",
"removing_team": "удаление из Команды",
"moving_channel_to_team": "перемещение канала в Команду",
"deleting_team": "удаление Команды",
"member-does-not-exist": "Участник не существует",
"Convert": "Конвертировать",
"Convert_to_Team": "Конвертировать в команду",
"Convert_to_Team_Warning": "Это нельзя отменить. После преобразования канала в Команду, вы не сможете преобразовать его обратно в канал.",
"Move_to_Team": "Перенести в команду",
"Move_Channel_Paragraph": "Перемещение канала внутрь Команды означает, что этот канал будет добавлен в контекст Команды, однако все участники канала, которые не являются членами соответствующей Команды, по-прежнему будут иметь доступ к этому каналу, но не будут добавлены как участники Команды \n\nВсе управление каналом по-прежнему будет осуществляться владельцами этого канала.\n\nЧлены Команды и даже владельцы Команды, если они не являются членами этого канала, не могут иметь доступ к содержимому канала \n\nОбратите внимание, что владелец Команды сможет удалять участников с канала.",
"Move_to_Team_Warning": "После прочтения предыдущих инструкций об этом поведении, вы все еще хотите переместить этот канал в выбранную Команду?",
"Load_More": "Загрузить еще",
"Load_Newer": "Загрузить более позднее",
"Load_Older": "Загрузить более раннее"
} }

View File

@ -290,6 +290,7 @@
"last_message": "son ileti", "last_message": "son ileti",
"Leave_channel": "Kanaldan ayrıl", "Leave_channel": "Kanaldan ayrıl",
"leaving_room": "odadan ayrılıyor", "leaving_room": "odadan ayrılıyor",
"Leave": "Odadan ayrıl",
"leave": "ayrıl", "leave": "ayrıl",
"Legal": "Yasal", "Legal": "Yasal",
"Light": "Açık", "Light": "Açık",
@ -440,7 +441,6 @@
"Room_changed_announcement": "Oda duyurusu, {{userBy}} tarafından {{announcement}} olarak değiştirildi", "Room_changed_announcement": "Oda duyurusu, {{userBy}} tarafından {{announcement}} olarak değiştirildi",
"Room_changed_avatar": "Oda profil fotoğrafı {{userBy}} tarafından değiştirildi", "Room_changed_avatar": "Oda profil fotoğrafı {{userBy}} tarafından değiştirildi",
"Room_changed_description": "Oda açıklaması, {{userBy}} tarafından {{description}} olarak değiştirildi", "Room_changed_description": "Oda açıklaması, {{userBy}} tarafından {{description}} olarak değiştirildi",
"Room_changed_privacy": "Oda açıklaması, {{userBy}} tarafından {{description}} olarak değiştirildi",
"Room_changed_topic": "Oda konusu, {{userBy}} tarafından {{topic}} olarak değiştirildi", "Room_changed_topic": "Oda konusu, {{userBy}} tarafından {{topic}} olarak değiştirildi",
"Room_Files": "Oda Dosyaları", "Room_Files": "Oda Dosyaları",
"Room_Info_Edit": "Oda Bilgilerini Düzenle", "Room_Info_Edit": "Oda Bilgilerini Düzenle",
@ -565,7 +565,6 @@
"Username": "Kullanıcı adı", "Username": "Kullanıcı adı",
"Username_or_email": "Kullanıcı adı ya da e-posta", "Username_or_email": "Kullanıcı adı ya da e-posta",
"Uses_server_configuration": "Sunucu yapılandırmasını kullanır", "Uses_server_configuration": "Sunucu yapılandırmasını kullanır",
"Usually_a_discussion_starts_with_a_question_like_How_do_I_upload_a_picture": "Genellikle tartışma, \"Nasıl resim yüklerim?\" gibi bir soruyla başlar.",
"Validating": "Doğrulanıyor", "Validating": "Doğrulanıyor",
"Registration_Succeeded": "Kayıt Başarılı!", "Registration_Succeeded": "Kayıt Başarılı!",
"Verify": "Onayla", "Verify": "Onayla",
@ -600,7 +599,6 @@
"You_need_to_access_at_least_one_RocketChat_server_to_share_something": "Bir şeyler paylaşmak için Rocket.Chat sunucusuna erişmeniz gerekir.", "You_need_to_access_at_least_one_RocketChat_server_to_share_something": "Bir şeyler paylaşmak için Rocket.Chat sunucusuna erişmeniz gerekir.",
"You_need_to_verifiy_your_email_address_to_get_notications": "Bildirim almak için e-posta adresinizi doğrulamanız gerekiyor", "You_need_to_verifiy_your_email_address_to_get_notications": "Bildirim almak için e-posta adresinizi doğrulamanız gerekiyor",
"Your_certificate": "Sertifikanız", "Your_certificate": "Sertifikanız",
"Your_message": "İletiınız",
"Your_invite_link_will_expire_after__usesLeft__uses": "Davet bağlantınızın geçerliliği {{usesLeft}} kullanımdan sonra sona erecek.", "Your_invite_link_will_expire_after__usesLeft__uses": "Davet bağlantınızın geçerliliği {{usesLeft}} kullanımdan sonra sona erecek.",
"Your_invite_link_will_expire_on__date__or_after__usesLeft__uses": "Davet bağlantınızın geçerliliği {{date}} tarihinde veya {{usesLeft}} kullanımdan sonra sona erecek.", "Your_invite_link_will_expire_on__date__or_after__usesLeft__uses": "Davet bağlantınızın geçerliliği {{date}} tarihinde veya {{usesLeft}} kullanımdan sonra sona erecek.",
"Your_invite_link_will_expire_on__date__": "Davet bağlantınızın geçerlilik süresi {{date}} tarihinde sona erecek.", "Your_invite_link_will_expire_on__date__": "Davet bağlantınızın geçerlilik süresi {{date}} tarihinde sona erecek.",
@ -683,28 +681,23 @@
"No_threads_following": "Herhangi bir konuyu takip etmiyorsunuz", "No_threads_following": "Herhangi bir konuyu takip etmiyorsunuz",
"No_threads_unread": "Okunmamış konu yok", "No_threads_unread": "Okunmamış konu yok",
"Messagebox_Send_to_channel": "Kanala gönder", "Messagebox_Send_to_channel": "Kanala gönder",
"Set_as_leader": "Lider olarak ayarla",
"Set_as_moderator": "Moderatör olarak ayarla",
"Set_as_owner": "Sahip olarak ayarla",
"Remove_as_leader": "Lider olarak kaldır",
"Remove_as_moderator": "Moderatör olarak kaldır",
"Remove_as_owner": "Sahip olarak kaldır",
"Remove_from_room": "Odadan çıkar", "Remove_from_room": "Odadan çıkar",
"Ignore": "Yok say", "Ignore": "Yok say",
"Unignore": "Yok sayma", "Unignore": "Yok sayma",
"User_has_been_ignored": "Kullanıcı yok sayıldı.", "User_has_been_ignored": "Kullanıcı yok sayıldı.",
"User_has_been_unignored": "Kullanıcı artık yok sayılmıyor.", "User_has_been_unignored": "Kullanıcı artık yok sayılmıyor.",
"User_has_been_removed_from_s": "Kullanıcı {{s}} alanından kaldırıldı.", "User_has_been_removed_from_s": "Kullanıcı {{s}} alanından kaldırıldı.",
"User__username__is_now_a_leader_of__room_name_": "{{Username}} kullanıcısı artık {{room_name}} lideridir.", "User__username__is_now_a_leader_of__room_name_": "{{username}} kullanıcısı artık {{room_name}} lideridir.",
"User__username__is_now_a_moderator_of__room_name_": "{{Username}} kullanıcısı artık bir {{room_name}} moderatörüdür.", "User__username__is_now_a_moderator_of__room_name_": "{{username}} kullanıcısı artık bir {{room_name}} moderatörüdür.",
"User__username__is_now_a_owner_of__room_name_": "{{Username}} kullanıcısı artık {{room_name}} adlı odanın sahibidir.", "User__username__is_now_a_owner_of__room_name_": "{{username}} kullanıcısı artık {{room_name}} adlı odanın sahibidir.",
"User__username__removed_from__room_name__leaders": "{{Username}} adlı kullanıcı, {{room_name}} liderlerinden kaldırıldı.", "User__username__removed_from__room_name__leaders": "{{username}} adlı kullanıcı, {{room_name}} liderlerinden kaldırıldı.",
"User__username__removed_from__room_name__moderators": "{{Username}} adlı kullanıcı, {{room_name}} moderatörlerinden kaldırıldı.", "User__username__removed_from__room_name__moderators": "{{username}} adlı kullanıcı, {{room_name}} moderatörlerinden kaldırıldı.",
"User__username__removed_from__room_name__owners": "{{Username}} adlı kullanıcı, {{room_name}} sahiplerinden kaldırıldı.", "User__username__removed_from__room_name__owners": "{{username}} adlı kullanıcı, {{room_name}} sahiplerinden kaldırıldı.",
"The_user_will_be_removed_from_s": "Kullanıcı, {{s}} alanından kaldırılacak!", "The_user_will_be_removed_from_s": "Kullanıcı, {{s}} alanından kaldırılacak!",
"Yes_remove_user": "Evet, kullanıcıyı kaldır!", "Yes_remove_user": "Evet, kullanıcıyı kaldır!",
"Direct_message": "Özel ileti", "Direct_message": "Özel ileti",
"Message_Ignored": "İleti yok sayıldı. Görüntülemek için dokunun.", "Message_Ignored": "İleti yok sayıldı. Görüntülemek için dokunun.",
"Enter_workspace_URL": "Çalışma alanı URL'nizi girin", "Enter_workspace_URL": "Çalışma alanı URL'nizi girin",
"Workspace_URL_Example": "Örn. sirketiniz.rocket.chat" "Workspace_URL_Example": "Örn. sirketiniz.rocket.chat",
"invalid-room": "Geçersiz oda"
} }

View File

@ -33,7 +33,7 @@
"error-invalid-date": "无效的日期", "error-invalid-date": "无效的日期",
"error-invalid-description": "无效的描述", "error-invalid-description": "无效的描述",
"error-invalid-domain": "无效的域名", "error-invalid-domain": "无效的域名",
"error-invalid-email": "无效的电子邮件{{emai}}", "error-invalid-email": "无效的电子邮件{{email}}",
"error-invalid-email-address": "无效的邮件地址", "error-invalid-email-address": "无效的邮件地址",
"error-invalid-file-height": "无效的文件长度", "error-invalid-file-height": "无效的文件长度",
"error-invalid-file-type": "无效的文件类型", "error-invalid-file-type": "无效的文件类型",
@ -278,11 +278,11 @@
"is_typing": "正在输入", "is_typing": "正在输入",
"Invalid_or_expired_invite_token": "无效或到期的邀请 token", "Invalid_or_expired_invite_token": "无效或到期的邀请 token",
"Invalid_server_version": "此 App 版本已不支援您正在连线之服务器版本。当前版本: {{currentVersion}}.\\n\\n最低版本要求: {{minVersion}}", "Invalid_server_version": "此 App 版本已不支援您正在连线之服务器版本。当前版本: {{currentVersion}}.\\n\\n最低版本要求: {{minVersion}}",
"Join_your_workspace": "加入您的工作区",
"Invite_Link": "邀请链接", "Invite_Link": "邀请链接",
"Invite_users": "邀请用戶", "Invite_users": "邀请用戶",
"Join": "加入", "Join": "加入",
"Join_our_open_workspace": "加入开放工作区", "Join_our_open_workspace": "加入开放工作区",
"Join_your_workspace": "加入您的工作区",
"Just_invited_people_can_access_this_channel": "仅有被邀请人能进入这个频道", "Just_invited_people_can_access_this_channel": "仅有被邀请人能进入这个频道",
"Language": "语言", "Language": "语言",
"last_message": "最后一条信息", "last_message": "最后一条信息",
@ -300,7 +300,7 @@
"Logging_out": "正在登出", "Logging_out": "正在登出",
"Logout": "注销", "Logout": "注销",
"Max_number_of_uses": "最大使用次数", "Max_number_of_uses": "最大使用次数",
"Max_number_of_users_allowed_is_number": "允许使用者上限数量", "Max_number_of_users_allowed_is_number": "允许使用者上限数量{{maxUsers}}",
"members": "成员", "members": "成员",
"Members": "成员", "Members": "成员",
"Mentioned_Messages": "被提及的信息", "Mentioned_Messages": "被提及的信息",
@ -444,7 +444,7 @@
"Room_Info_Edit": "聊天室信息编辑", "Room_Info_Edit": "聊天室信息编辑",
"Room_Info": "聊天室信息", "Room_Info": "聊天室信息",
"Room_Members": "聊天室成员", "Room_Members": "聊天室成员",
"Room_name_changed": "{{userBy}} 将聊天室名称改为:{{{name}}", "Room_name_changed": "{{userBy}} 将聊天室名称改为:{{name}}",
"SAVE": "保存", "SAVE": "保存",
"Save_Changes": "保存更改", "Save_Changes": "保存更改",
"Save": "保存", "Save": "保存",

View File

@ -278,16 +278,17 @@
"is_typing": "正在輸入", "is_typing": "正在輸入",
"Invalid_or_expired_invite_token": "無效或到期的邀請 token", "Invalid_or_expired_invite_token": "無效或到期的邀請 token",
"Invalid_server_version": "此 App 版本已不支援您正在連線之伺服器版本。當前版本: {{currentVersion}}.\\n\\n最低版本要求: {{minVersion}}", "Invalid_server_version": "此 App 版本已不支援您正在連線之伺服器版本。當前版本: {{currentVersion}}.\\n\\n最低版本要求: {{minVersion}}",
"Join_your_workspace": "加入您的工作區",
"Invite_Link": "邀請連結", "Invite_Link": "邀請連結",
"Invite_users": "邀請使用者", "Invite_users": "邀請使用者",
"Join": "加入", "Join": "加入",
"Join_our_open_workspace": "加入開放工作區", "Join_our_open_workspace": "加入開放工作區",
"Join_your_workspace": "加入您的工作區",
"Just_invited_people_can_access_this_channel": "僅有受邀者能存取此頻道", "Just_invited_people_can_access_this_channel": "僅有受邀者能存取此頻道",
"Language": "語言", "Language": "語言",
"last_message": "最後一則訊息", "last_message": "最後一則訊息",
"Leave_channel": "離開頻道", "Leave_channel": "離開頻道",
"leaving_room": "離開聊天室", "leaving_room": "離開聊天室",
"Leave": "離開",
"leave": "離開", "leave": "離開",
"Legal": "合法", "Legal": "合法",
"Light": "淺色", "Light": "淺色",
@ -300,7 +301,7 @@
"Logging_out": "正在登出", "Logging_out": "正在登出",
"Logout": "登出", "Logout": "登出",
"Max_number_of_uses": "最大使用次數", "Max_number_of_uses": "最大使用次數",
"Max_number_of_users_allowed_is_number": "允許使用者上限數量", "Max_number_of_users_allowed_is_number": "允許使用者上限數量 {{maxUsers}}",
"members": "成員", "members": "成員",
"Members": "成員", "Members": "成員",
"Mentioned_Messages": "被提及的訊息", "Mentioned_Messages": "被提及的訊息",
@ -444,7 +445,7 @@
"Room_Info_Edit": "修改聊天室資訊", "Room_Info_Edit": "修改聊天室資訊",
"Room_Info": "聊天室資訊", "Room_Info": "聊天室資訊",
"Room_Members": "聊天室成員", "Room_Members": "聊天室成員",
"Room_name_changed": "{{userBy}} 將聊天室名稱改為:{{{name}}", "Room_name_changed": "{{userBy}} 將聊天室名稱改為:{{name}}",
"SAVE": "儲存", "SAVE": "儲存",
"Save_Changes": "儲存更改", "Save_Changes": "儲存更改",
"Save": "儲存", "Save": "儲存",
@ -678,5 +679,7 @@
"No_threads": "當前沒有討論串", "No_threads": "當前沒有討論串",
"No_threads_following": "當前沒有正在追蹤的討論", "No_threads_following": "當前沒有正在追蹤的討論",
"No_threads_unread": "當前沒有未讀的討論", "No_threads_unread": "當前沒有未讀的討論",
"Messagebox_Send_to_channel": "發送至頻道" "Messagebox_Send_to_channel": "發送至頻道",
"Confirmation": "確認",
"invalid-room": "無效的房間"
} }

View File

@ -5,8 +5,10 @@ import {
import { sanitizer } from '../utils'; import { sanitizer } from '../utils';
export const TABLE_NAME = 'messages';
export default class Message extends Model { export default class Message extends Model {
static table = 'messages'; static table = TABLE_NAME;
static associations = { static associations = {
subscriptions: { type: 'belongs_to', key: 'rid' } subscriptions: { type: 'belongs_to', key: 'rid' }

View File

@ -4,8 +4,10 @@ import {
} from '@nozbe/watermelondb/decorators'; } from '@nozbe/watermelondb/decorators';
import { sanitizer } from '../utils'; import { sanitizer } from '../utils';
export const TABLE_NAME = 'subscriptions';
export default class Subscription extends Model { export default class Subscription extends Model {
static table = 'subscriptions'; static table = TABLE_NAME;
static associations = { static associations = {
messages: { type: 'has_many', foreignKey: 'rid' }, messages: { type: 'has_many', foreignKey: 'rid' },

View File

@ -5,8 +5,10 @@ import {
import { sanitizer } from '../utils'; import { sanitizer } from '../utils';
export const TABLE_NAME = 'threads';
export default class Thread extends Model { export default class Thread extends Model {
static table = 'threads'; static table = TABLE_NAME;
static associations = { static associations = {
subscriptions: { type: 'belongs_to', key: 'rid' } subscriptions: { type: 'belongs_to', key: 'rid' }

View File

@ -5,8 +5,10 @@ import {
import { sanitizer } from '../utils'; import { sanitizer } from '../utils';
export const TABLE_NAME = 'thread_messages';
export default class ThreadMessage extends Model { export default class ThreadMessage extends Model {
static table = 'thread_messages'; static table = TABLE_NAME;
static associations = { static associations = {
subscriptions: { type: 'belongs_to', key: 'subscription_id' } subscriptions: { type: 'belongs_to', key: 'subscription_id' }

View File

@ -59,7 +59,7 @@ export default appSchema({
{ name: 'e2e_key_id', type: 'string', isOptional: true }, { name: 'e2e_key_id', type: 'string', isOptional: true },
{ name: 'avatar_etag', type: 'string', isOptional: true }, { name: 'avatar_etag', type: 'string', isOptional: true },
{ name: 'team_id', type: 'string', isIndexed: true }, { name: 'team_id', type: 'string', isIndexed: true },
{ name: 'team_main', type: 'boolean', isOptional: true } { name: 'team_main', type: 'boolean', isOptional: true } // Use `Q.notEq(true)` to get false or null
] ]
}), }),
tableSchema({ tableSchema({

View File

@ -0,0 +1,15 @@
import database from '..';
import { TABLE_NAME } from '../model/Message';
const getCollection = db => db.get(TABLE_NAME);
export const getMessageById = async(messageId) => {
const db = database.active;
const messageCollection = getCollection(db);
try {
const result = await messageCollection.find(messageId);
return result;
} catch (error) {
return null;
}
};

View File

@ -0,0 +1,15 @@
import database from '..';
import { TABLE_NAME } from '../model/Subscription';
const getCollection = db => db.get(TABLE_NAME);
export const getSubscriptionByRoomId = async(rid) => {
const db = database.active;
const subCollection = getCollection(db);
try {
const result = await subCollection.find(rid);
return result;
} catch (error) {
return null;
}
};

View File

@ -0,0 +1,15 @@
import database from '..';
import { TABLE_NAME } from '../model/Thread';
const getCollection = db => db.get(TABLE_NAME);
export const getThreadById = async(tmid) => {
const db = database.active;
const threadCollection = getCollection(db);
try {
const result = await threadCollection.find(tmid);
return result;
} catch (error) {
return null;
}
};

View File

@ -0,0 +1,15 @@
import database from '..';
import { TABLE_NAME } from '../model/ThreadMessage';
const getCollection = db => db.get(TABLE_NAME);
export const getThreadMessageById = async(messageId) => {
const db = database.active;
const threadMessageCollection = getCollection(db);
try {
const result = await threadMessageCollection.find(messageId);
return result;
} catch (error) {
return null;
}
};

View File

@ -13,19 +13,25 @@ const PERMISSIONS = [
'add-user-to-any-c-room', 'add-user-to-any-c-room',
'add-user-to-any-p-room', 'add-user-to-any-p-room',
'add-user-to-joined-room', 'add-user-to-joined-room',
'add-team-channel',
'archive-room', 'archive-room',
'auto-translate', 'auto-translate',
'create-invite-links', 'create-invite-links',
'create-team',
'delete-c', 'delete-c',
'delete-message', 'delete-message',
'delete-p', 'delete-p',
'delete-team',
'edit-message', 'edit-message',
'edit-room', 'edit-room',
'edit-team-member',
'edit-team-channel',
'force-delete-message', 'force-delete-message',
'mute-user', 'mute-user',
'pin-message', 'pin-message',
'post-readonly', 'post-readonly',
'remove-user', 'remove-user',
'remove-team-channel',
'set-leader', 'set-leader',
'set-moderator', 'set-moderator',
'set-owner', 'set-owner',
@ -38,7 +44,9 @@ const PERMISSIONS = [
'view-privileged-setting', 'view-privileged-setting',
'view-room-administration', 'view-room-administration',
'view-statistics', 'view-statistics',
'view-user-administration' 'view-user-administration',
'view-all-teams',
'view-all-team-channels'
]; ];
export async function setPermissions() { export async function setPermissions() {

View File

@ -0,0 +1,29 @@
import { getSubscriptionByRoomId } from '../database/services/Subscription';
import RocketChat from '../rocketchat';
const getRoomInfo = async(rid) => {
let result;
result = await getSubscriptionByRoomId(rid);
if (result) {
return {
rid,
name: result.name,
fname: result.fname,
t: result.t
};
}
result = await RocketChat.getRoomInfo(rid);
if (result?.success) {
return {
rid,
name: result.room.name,
fname: result.room.fname,
t: result.room.t
};
}
return null;
};
export default getRoomInfo;

View File

@ -0,0 +1,15 @@
import RocketChat from '../rocketchat';
const getSingleMessage = messageId => new Promise(async(resolve, reject) => {
try {
const result = await RocketChat.getSingleMessage(messageId);
if (result.success) {
return resolve(result.message);
}
return reject();
} catch (e) {
return reject();
}
});
export default getSingleMessage;

View File

@ -0,0 +1,49 @@
import { sanitizedRaw } from '@nozbe/watermelondb/RawRecord';
import database from '../database';
import { getMessageById } from '../database/services/Message';
import { getThreadById } from '../database/services/Thread';
import log from '../../utils/log';
import getSingleMessage from './getSingleMessage';
import { Encryption } from '../encryption';
const buildThreadName = thread => thread.msg || thread?.attachments?.[0]?.title;
const getThreadName = async(rid, tmid, messageId) => {
let tmsg;
try {
const db = database.active;
const threadCollection = db.get('threads');
const messageRecord = await getMessageById(messageId);
const threadRecord = await getThreadById(tmid);
if (threadRecord) {
tmsg = buildThreadName(threadRecord);
await db.action(async() => {
await messageRecord?.update((m) => {
m.tmsg = tmsg;
});
});
} else {
let thread = await getSingleMessage(tmid);
thread = await Encryption.decryptMessage(thread);
tmsg = buildThreadName(thread);
await db.action(async() => {
await db.batch(
threadCollection?.prepareCreate((t) => {
t._raw = sanitizedRaw({ id: thread._id }, threadCollection.schema);
t.subscription.id = rid;
Object.assign(t, thread);
}),
messageRecord?.prepareUpdate((m) => {
m.tmsg = tmsg;
})
);
});
}
} catch (e) {
log(e);
}
return tmsg;
};
export default getThreadName;

View File

@ -1,8 +1,15 @@
import moment from 'moment';
import { MESSAGE_TYPE_LOAD_MORE } from '../../constants/messageTypeLoad';
import log from '../../utils/log'; import log from '../../utils/log';
import { getMessageById } from '../database/services/Message';
import updateMessages from './updateMessages'; import updateMessages from './updateMessages';
import { generateLoadMoreId } from '../utils';
const COUNT = 50;
async function load({ rid: roomId, latest, t }) { async function load({ rid: roomId, latest, t }) {
let params = { roomId, count: 50 }; let params = { roomId, count: COUNT };
if (latest) { if (latest) {
params = { ...params, latest: new Date(latest).toISOString() }; params = { ...params, latest: new Date(latest).toISOString() };
} }
@ -24,9 +31,20 @@ export default function loadMessagesForRoom(args) {
return new Promise(async(resolve, reject) => { return new Promise(async(resolve, reject) => {
try { try {
const data = await load.call(this, args); const data = await load.call(this, args);
if (data?.length) {
if (data && data.length) { const lastMessage = data[data.length - 1];
await updateMessages({ rid: args.rid, update: data }); const lastMessageRecord = await getMessageById(lastMessage._id);
if (!lastMessageRecord && data.length === COUNT) {
const loadMoreItem = {
_id: generateLoadMoreId(lastMessage._id),
rid: lastMessage.rid,
ts: moment(lastMessage.ts).subtract(1, 'millisecond'),
t: MESSAGE_TYPE_LOAD_MORE,
msg: lastMessage.msg
};
data.push(loadMoreItem);
}
await updateMessages({ rid: args.rid, update: data, loaderItem: args.loaderItem });
return resolve(data); return resolve(data);
} else { } else {
return resolve([]); return resolve([]);

View File

@ -0,0 +1,42 @@
import EJSON from 'ejson';
import moment from 'moment';
import orderBy from 'lodash/orderBy';
import log from '../../utils/log';
import updateMessages from './updateMessages';
import { getMessageById } from '../database/services/Message';
import { MESSAGE_TYPE_LOAD_NEXT_CHUNK } from '../../constants/messageTypeLoad';
import { generateLoadMoreId } from '../utils';
const COUNT = 50;
export default function loadNextMessages(args) {
return new Promise(async(resolve, reject) => {
try {
const data = await this.methodCallWrapper('loadNextMessages', args.rid, args.ts, COUNT);
let messages = EJSON.fromJSONValue(data?.messages);
messages = orderBy(messages, 'ts');
if (messages?.length) {
const lastMessage = messages[messages.length - 1];
const lastMessageRecord = await getMessageById(lastMessage._id);
if (!lastMessageRecord && messages.length === COUNT) {
const loadMoreItem = {
_id: generateLoadMoreId(lastMessage._id),
rid: lastMessage.rid,
tmid: args.tmid,
ts: moment(lastMessage.ts).add(1, 'millisecond'),
t: MESSAGE_TYPE_LOAD_NEXT_CHUNK
};
messages.push(loadMoreItem);
}
await updateMessages({ rid: args.rid, update: messages, loaderItem: args.loaderItem });
return resolve(messages);
} else {
return resolve([]);
}
} catch (e) {
log(e);
reject(e);
}
});
}

View File

@ -0,0 +1,65 @@
import EJSON from 'ejson';
import moment from 'moment';
import orderBy from 'lodash/orderBy';
import log from '../../utils/log';
import updateMessages from './updateMessages';
import { getMessageById } from '../database/services/Message';
import { MESSAGE_TYPE_LOAD_NEXT_CHUNK, MESSAGE_TYPE_LOAD_PREVIOUS_CHUNK } from '../../constants/messageTypeLoad';
import { generateLoadMoreId } from '../utils';
const COUNT = 50;
export default function loadSurroundingMessages({ messageId, rid }) {
return new Promise(async(resolve, reject) => {
try {
const data = await this.methodCallWrapper('loadSurroundingMessages', { _id: messageId, rid }, COUNT);
let messages = EJSON.fromJSONValue(data?.messages);
messages = orderBy(messages, 'ts');
const message = messages.find(m => m._id === messageId);
const { tmid } = message;
if (messages?.length) {
if (data?.moreBefore) {
const firstMessage = messages[0];
const firstMessageRecord = await getMessageById(firstMessage._id);
if (!firstMessageRecord) {
const loadMoreItem = {
_id: generateLoadMoreId(firstMessage._id),
rid: firstMessage.rid,
tmid,
ts: moment(firstMessage.ts).subtract(1, 'millisecond'),
t: MESSAGE_TYPE_LOAD_PREVIOUS_CHUNK,
msg: firstMessage.msg
};
messages.unshift(loadMoreItem);
}
}
if (data?.moreAfter) {
const lastMessage = messages[messages.length - 1];
const lastMessageRecord = await getMessageById(lastMessage._id);
if (!lastMessageRecord) {
const loadMoreItem = {
_id: generateLoadMoreId(lastMessage._id),
rid: lastMessage.rid,
tmid,
ts: moment(lastMessage.ts).add(1, 'millisecond'),
t: MESSAGE_TYPE_LOAD_NEXT_CHUNK,
msg: lastMessage.msg
};
messages.push(loadMoreItem);
}
}
await updateMessages({ rid, update: messages });
return resolve(messages);
} else {
return resolve([]);
}
} catch (e) {
log(e);
reject(e);
}
});
}

View File

@ -1,5 +1,6 @@
import { Q } from '@nozbe/watermelondb'; import { Q } from '@nozbe/watermelondb';
import { sanitizedRaw } from '@nozbe/watermelondb/RawRecord'; import { sanitizedRaw } from '@nozbe/watermelondb/RawRecord';
import EJSON from 'ejson';
import buildMessage from './helpers/buildMessage'; import buildMessage from './helpers/buildMessage';
import database from '../database'; import database from '../database';
@ -7,30 +8,27 @@ import log from '../../utils/log';
import protectedFunction from './helpers/protectedFunction'; import protectedFunction from './helpers/protectedFunction';
import { Encryption } from '../encryption'; import { Encryption } from '../encryption';
async function load({ tmid, offset }) { async function load({ tmid }) {
try { try {
// RC 1.0 // RC 1.0
const result = await this.sdk.get('chat.getThreadMessages', { const result = await this.methodCallWrapper('getThreadMessages', { tmid });
tmid, count: 50, offset, sort: { ts: -1 }, query: { _hidden: { $ne: true } } if (!result) {
});
if (!result || !result.success) {
return []; return [];
} }
return result.messages; return EJSON.fromJSONValue(result);
} catch (error) { } catch (error) {
console.log(error); console.log(error);
return []; return [];
} }
} }
export default function loadThreadMessages({ tmid, rid, offset = 0 }) { export default function loadThreadMessages({ tmid, rid }) {
return new Promise(async(resolve, reject) => { return new Promise(async(resolve, reject) => {
try { try {
let data = await load.call(this, { tmid, offset }); let data = await load.call(this, { tmid });
if (data && data.length) { if (data && data.length) {
try { try {
data = data.map(m => buildMessage(m)); data = data.filter(m => m.tmid).map(m => buildMessage(m));
data = await Encryption.decryptMessages(data); data = await Encryption.decryptMessages(data);
const db = database.active; const db = database.active;
const threadMessagesCollection = db.get('thread_messages'); const threadMessagesCollection = db.get('thread_messages');

View File

@ -159,7 +159,7 @@ export default class RoomSubscription {
updateMessage = message => ( updateMessage = message => (
new Promise(async(resolve) => { new Promise(async(resolve) => {
if (this.rid !== message.rid) { if (this.rid !== message.rid) {
return; return resolve();
} }
const db = database.active; const db = database.active;

View File

@ -6,8 +6,12 @@ import log from '../../utils/log';
import database from '../database'; import database from '../database';
import protectedFunction from './helpers/protectedFunction'; import protectedFunction from './helpers/protectedFunction';
import { Encryption } from '../encryption'; import { Encryption } from '../encryption';
import { MESSAGE_TYPE_ANY_LOAD } from '../../constants/messageTypeLoad';
import { generateLoadMoreId } from '../utils';
export default function updateMessages({ rid, update = [], remove = [] }) { export default function updateMessages({
rid, update = [], remove = [], loaderItem
}) {
try { try {
if (!((update && update.length) || (remove && remove.length))) { if (!((update && update.length) || (remove && remove.length))) {
return; return;
@ -30,7 +34,13 @@ export default function updateMessages({ rid, update = [], remove = [] }) {
const threadCollection = db.get('threads'); const threadCollection = db.get('threads');
const threadMessagesCollection = db.get('thread_messages'); const threadMessagesCollection = db.get('thread_messages');
const allMessagesRecords = await msgCollection const allMessagesRecords = await msgCollection
.query(Q.where('rid', rid), Q.where('id', Q.oneOf(messagesIds))) .query(
Q.where('rid', rid),
Q.or(
Q.where('id', Q.oneOf(messagesIds)),
Q.where('t', Q.oneOf(MESSAGE_TYPE_ANY_LOAD))
)
)
.fetch(); .fetch();
const allThreadsRecords = await threadCollection const allThreadsRecords = await threadCollection
.query(Q.where('rid', rid), Q.where('id', Q.oneOf(messagesIds))) .query(Q.where('rid', rid), Q.where('id', Q.oneOf(messagesIds)))
@ -55,6 +65,9 @@ export default function updateMessages({ rid, update = [], remove = [] }) {
let threadMessagesToCreate = allThreadMessages.filter(i1 => !allThreadMessagesRecords.find(i2 => i1._id === i2.id)); let threadMessagesToCreate = allThreadMessages.filter(i1 => !allThreadMessagesRecords.find(i2 => i1._id === i2.id));
let threadMessagesToUpdate = allThreadMessagesRecords.filter(i1 => allThreadMessages.find(i2 => i1.id === i2._id)); let threadMessagesToUpdate = allThreadMessagesRecords.filter(i1 => allThreadMessages.find(i2 => i1.id === i2._id));
// filter loaders to delete
let loadersToDelete = allMessagesRecords.filter(i1 => update.find(i2 => i1.id === generateLoadMoreId(i2._id)));
// Create // Create
msgsToCreate = msgsToCreate.map(message => msgCollection.prepareCreate(protectedFunction((m) => { msgsToCreate = msgsToCreate.map(message => msgCollection.prepareCreate(protectedFunction((m) => {
m._raw = sanitizedRaw({ id: message._id }, msgCollection.schema); m._raw = sanitizedRaw({ id: message._id }, msgCollection.schema);
@ -121,6 +134,12 @@ export default function updateMessages({ rid, update = [], remove = [] }) {
threadMessagesToDelete = threadMessagesToDelete.map(tm => tm.prepareDestroyPermanently()); threadMessagesToDelete = threadMessagesToDelete.map(tm => tm.prepareDestroyPermanently());
} }
// Delete loaders
loadersToDelete = loadersToDelete.map(m => m.prepareDestroyPermanently());
if (loaderItem) {
loadersToDelete.push(loaderItem.prepareDestroyPermanently());
}
const allRecords = [ const allRecords = [
...msgsToCreate, ...msgsToCreate,
...msgsToUpdate, ...msgsToUpdate,
@ -130,7 +149,8 @@ export default function updateMessages({ rid, update = [], remove = [] }) {
...threadsToDelete, ...threadsToDelete,
...threadMessagesToCreate, ...threadMessagesToCreate,
...threadMessagesToUpdate, ...threadMessagesToUpdate,
...threadMessagesToDelete ...threadMessagesToDelete,
...loadersToDelete
]; ];
try { try {

View File

@ -1,4 +1,5 @@
import { InteractionManager } from 'react-native'; import { InteractionManager } from 'react-native';
import EJSON from 'ejson';
import { import {
Rocketchat as RocketchatClient, Rocketchat as RocketchatClient,
settings as RocketChatSettings settings as RocketChatSettings
@ -41,6 +42,8 @@ import canOpenRoom from './methods/canOpenRoom';
import triggerBlockAction, { triggerSubmitView, triggerCancel } from './methods/actions'; import triggerBlockAction, { triggerSubmitView, triggerCancel } from './methods/actions';
import loadMessagesForRoom from './methods/loadMessagesForRoom'; import loadMessagesForRoom from './methods/loadMessagesForRoom';
import loadSurroundingMessages from './methods/loadSurroundingMessages';
import loadNextMessages from './methods/loadNextMessages';
import loadMissedMessages from './methods/loadMissedMessages'; import loadMissedMessages from './methods/loadMissedMessages';
import loadThreadMessages from './methods/loadThreadMessages'; import loadThreadMessages from './methods/loadThreadMessages';
@ -60,6 +63,7 @@ import UserPreferences from './userPreferences';
import { Encryption } from './encryption'; import { Encryption } from './encryption';
import EventEmitter from '../utils/events'; import EventEmitter from '../utils/events';
import { sanitizeLikeString } from './database/utils'; import { sanitizeLikeString } from './database/utils';
import { TEAM_TYPE } from '../definition/ITeam';
const TOKEN_KEY = 'reactnativemeteor_usertoken'; const TOKEN_KEY = 'reactnativemeteor_usertoken';
const CURRENT_SERVER = 'currentServer'; const CURRENT_SERVER = 'currentServer';
@ -94,10 +98,19 @@ const RocketChat = {
}, },
canOpenRoom, canOpenRoom,
createChannel({ createChannel({
name, users, type, readOnly, broadcast, encrypted name, users, type, readOnly, broadcast, encrypted, teamId
}) { }) {
// RC 0.51.0 const params = {
return this.methodCallWrapper(type ? 'createPrivateGroup' : 'createChannel', name, users, readOnly, {}, { broadcast, encrypted }); name,
members: users,
readOnly,
extraData: {
broadcast,
encrypted,
...(teamId && { teamId })
}
};
return this.post(type ? 'groups.create' : 'channels.create', params);
}, },
async getWebsocketInfo({ server }) { async getWebsocketInfo({ server }) {
const sdk = new RocketchatClient({ host: server, protocol: 'ddp', useSsl: useSsl(server) }); const sdk = new RocketchatClient({ host: server, protocol: 'ddp', useSsl: useSsl(server) });
@ -196,6 +209,10 @@ const RocketChat = {
clearTimeout(this.connectTimeout); clearTimeout(this.connectTimeout);
} }
if (this.connectingListener) {
this.connectingListener.then(this.stopListener);
}
if (this.connectedListener) { if (this.connectedListener) {
this.connectedListener.then(this.stopListener); this.connectedListener.then(this.stopListener);
} }
@ -243,7 +260,7 @@ const RocketChat = {
sdkConnect(); sdkConnect();
this.connectedListener = this.sdk.onStreamData('connecting', () => { this.connectingListener = this.sdk.onStreamData('connecting', () => {
reduxStore.dispatch(connectRequest()); reduxStore.dispatch(connectRequest());
}); });
@ -610,6 +627,8 @@ const RocketChat = {
}, },
loadMissedMessages, loadMissedMessages,
loadMessagesForRoom, loadMessagesForRoom,
loadSurroundingMessages,
loadNextMessages,
loadThreadMessages, loadThreadMessages,
sendMessage, sendMessage,
getRooms, getRooms,
@ -643,7 +662,8 @@ const RocketChat = {
avatarETag: sub.avatarETag, avatarETag: sub.avatarETag,
t: sub.t, t: sub.t,
encrypted: sub.encrypted, encrypted: sub.encrypted,
lastMessage: sub.lastMessage lastMessage: sub.lastMessage,
...(sub.teamId && { teamId: sub.teamId })
})); }));
return data; return data;
@ -728,7 +748,74 @@ const RocketChat = {
prid, pmid, t_name, reply, users, encrypted prid, pmid, t_name, reply, users, encrypted
}); });
}, },
createTeam({
name, users, type, readOnly, broadcast, encrypted
}) {
const params = {
name,
users,
type: type ? TEAM_TYPE.PRIVATE : TEAM_TYPE.PUBLIC,
room: {
readOnly,
extraData: {
broadcast,
encrypted
}
}
};
// RC 3.13.0
return this.post('teams.create', params);
},
addRoomsToTeam({ teamId, rooms }) {
// RC 3.13.0
return this.post('teams.addRooms', { teamId, rooms });
},
removeTeamRoom({ roomId, teamId }) {
// RC 3.13.0
return this.post('teams.removeRoom', { roomId, teamId });
},
leaveTeam({ teamName, rooms }) {
// RC 3.13.0
return this.post('teams.leave', { teamName, rooms });
},
removeTeamMember({
teamId, teamName, userId, rooms
}) {
// RC 3.13.0
return this.post('teams.removeMember', {
teamId, teamName, userId, rooms
});
},
updateTeamRoom({ roomId, isDefault }) {
// RC 3.13.0
return this.post('teams.updateRoom', { roomId, isDefault });
},
deleteTeam({ teamId, roomsToRemove }) {
// RC 3.13.0
return this.post('teams.delete', { teamId, roomsToRemove });
},
teamListRoomsOfUser({ teamId, userId }) {
// RC 3.13.0
return this.sdk.get('teams.listRoomsOfUser', { teamId, userId });
},
getTeamInfo({ teamId }) {
// RC 3.13.0
return this.sdk.get('teams.info', { teamId });
},
convertChannelToTeam({ rid, name, type }) {
const params = {
...(type === 'c'
? {
channelId: rid,
channelName: name
}
: {
roomId: rid,
roomName: name
})
};
return this.sdk.post(type === 'c' ? 'channels.convertToTeam' : 'groups.convertToTeam', params);
},
joinRoom(roomId, joinCode, type) { joinRoom(roomId, joinCode, type) {
// TODO: join code // TODO: join code
// RC 0.48.0 // RC 0.48.0
@ -890,9 +977,15 @@ const RocketChat = {
methodCallWrapper(method, ...params) { methodCallWrapper(method, ...params) {
const { API_Use_REST_For_DDP_Calls } = reduxStore.getState().settings; const { API_Use_REST_For_DDP_Calls } = reduxStore.getState().settings;
if (API_Use_REST_For_DDP_Calls) { if (API_Use_REST_For_DDP_Calls) {
return this.post(`method.call/${ method }`, { message: JSON.stringify({ method, params }) }); return this.post(`method.call/${ method }`, { message: EJSON.stringify({ method, params }) });
} }
return this.methodCall(method, ...params); const parsedParams = params.map((param) => {
if (param instanceof Date) {
return { $date: new Date(param).getTime() };
}
return param;
});
return this.methodCall(method, ...parsedParams);
}, },
getUserRoles() { getUserRoles() {
@ -1136,7 +1229,7 @@ const RocketChat = {
methodCall(...args) { methodCall(...args) {
return new Promise(async(resolve, reject) => { return new Promise(async(resolve, reject) => {
try { try {
const result = await this.sdk.methodCall(...args, this.code || ''); const result = await this.sdk?.methodCall(...args, this.code || '');
return resolve(result); return resolve(result);
} catch (e) { } catch (e) {
if (e.error && (e.error === 'totp-required' || e.error === 'totp-invalid')) { if (e.error && (e.error === 'totp-required' || e.error === 'totp-invalid')) {

View File

@ -20,3 +20,5 @@ export const methods = {
}; };
export const compareServerVersion = (currentServerVersion, versionToCompare, func) => currentServerVersion && func(coerce(currentServerVersion), versionToCompare); export const compareServerVersion = (currentServerVersion, versionToCompare, func) => currentServerVersion && func(coerce(currentServerVersion), versionToCompare);
export const generateLoadMoreId = id => `load-more-${ id }`;

View File

@ -10,7 +10,7 @@ export const onNotification = (notification) => {
if (data) { if (data) {
try { try {
const { const {
rid, name, sender, type, host, messageType rid, name, sender, type, host, messageType, messageId
} = EJSON.parse(data.ejson); } = EJSON.parse(data.ejson);
const types = { const types = {
@ -24,6 +24,7 @@ export const onNotification = (notification) => {
const params = { const params = {
host, host,
rid, rid,
messageId,
path: `${ types[type] }/${ roomName }`, path: `${ types[type] }/${ roomName }`,
isCall: messageType === 'jitsi_call_started' isCall: messageType === 'jitsi_call_started'
}; };

View File

@ -18,7 +18,7 @@ const DirectoryItemLabel = React.memo(({ text, theme }) => {
}); });
const DirectoryItem = ({ const DirectoryItem = ({
title, description, avatar, onPress, testID, style, rightLabel, type, rid, theme title, description, avatar, onPress, testID, style, rightLabel, type, rid, theme, teamMain
}) => ( }) => (
<Touch <Touch
onPress={onPress} onPress={onPress}
@ -36,7 +36,7 @@ const DirectoryItem = ({
/> />
<View style={styles.directoryItemTextContainer}> <View style={styles.directoryItemTextContainer}>
<View style={styles.directoryItemTextTitle}> <View style={styles.directoryItemTextTitle}>
<RoomTypeIcon type={type} theme={theme} /> <RoomTypeIcon type={type} teamMain={teamMain} theme={theme} />
<Text style={[styles.directoryItemName, { color: themes[theme].titleText }]} numberOfLines={1}>{title}</Text> <Text style={[styles.directoryItemName, { color: themes[theme].titleText }]} numberOfLines={1}>{title}</Text>
</View> </View>
{ description ? <Text style={[styles.directoryItemUsername, { color: themes[theme].auxiliaryText }]} numberOfLines={1}>{description}</Text> : null } { description ? <Text style={[styles.directoryItemUsername, { color: themes[theme].auxiliaryText }]} numberOfLines={1}>{description}</Text> : null }
@ -56,7 +56,8 @@ DirectoryItem.propTypes = {
style: PropTypes.any, style: PropTypes.any,
rightLabel: PropTypes.string, rightLabel: PropTypes.string,
rid: PropTypes.string, rid: PropTypes.string,
theme: PropTypes.string theme: PropTypes.string,
teamMain: PropTypes.bool
}; };
DirectoryItemLabel.propTypes = { DirectoryItemLabel.propTypes = {

View File

@ -10,6 +10,8 @@ import LastMessage from './LastMessage';
import Title from './Title'; import Title from './Title';
import UpdatedAt from './UpdatedAt'; import UpdatedAt from './UpdatedAt';
import Touchable from './Touchable'; import Touchable from './Touchable';
import Tag from './Tag';
import I18n from '../../i18n';
const RoomItem = ({ const RoomItem = ({
rid, rid,
@ -42,13 +44,16 @@ const RoomItem = ({
testID, testID,
swipeEnabled, swipeEnabled,
onPress, onPress,
onLongPress,
toggleFav, toggleFav,
toggleRead, toggleRead,
hideChannel, hideChannel,
teamMain teamMain,
autoJoin
}) => ( }) => (
<Touchable <Touchable
onPress={onPress} onPress={onPress}
onLongPress={onLongPress}
width={width} width={width}
favorite={favorite} favorite={favorite}
toggleFav={toggleFav} toggleFav={toggleFav}
@ -88,6 +93,9 @@ const RoomItem = ({
hideUnreadStatus={hideUnreadStatus} hideUnreadStatus={hideUnreadStatus}
alert={alert} alert={alert}
/> />
{
autoJoin ? <Tag testID='auto-join-tag' name={I18n.t('Auto-join')} /> : null
}
<UpdatedAt <UpdatedAt
date={date} date={date}
theme={theme} theme={theme}
@ -132,6 +140,9 @@ const RoomItem = ({
hideUnreadStatus={hideUnreadStatus} hideUnreadStatus={hideUnreadStatus}
alert={alert} alert={alert}
/> />
{
autoJoin ? <Tag name={I18n.t('Auto-join')} /> : null
}
<UnreadBadge <UnreadBadge
unread={unread} unread={unread}
userMentions={userMentions} userMentions={userMentions}
@ -181,7 +192,9 @@ RoomItem.propTypes = {
toggleFav: PropTypes.func, toggleFav: PropTypes.func,
toggleRead: PropTypes.func, toggleRead: PropTypes.func,
onPress: PropTypes.func, onPress: PropTypes.func,
hideChannel: PropTypes.func onLongPress: PropTypes.func,
hideChannel: PropTypes.func,
autoJoin: PropTypes.bool
}; };
RoomItem.defaultProps = { RoomItem.defaultProps = {

View File

@ -0,0 +1,32 @@
import React from 'react';
import { Text, View } from 'react-native';
import PropTypes from 'prop-types';
import { themes } from '../../constants/colors';
import { useTheme } from '../../theme';
import styles from './styles';
const Tag = React.memo(({ name, testID }) => {
const { theme } = useTheme();
return (
<View style={[styles.tagContainer, { backgroundColor: themes[theme].borderColor }]}>
<Text
style={[
styles.tagText, { color: themes[theme].infoText }
]}
numberOfLines={1}
testID={testID}
>
{name}
</Text>
</View>
);
});
Tag.propTypes = {
name: PropTypes.string,
testID: PropTypes.string
};
export default Tag;

View File

@ -1,7 +1,9 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { Animated } from 'react-native'; import { Animated } from 'react-native';
import { PanGestureHandler, State } from 'react-native-gesture-handler'; import {
LongPressGestureHandler, PanGestureHandler, State
} from 'react-native-gesture-handler';
import Touch from '../../utils/touch'; import Touch from '../../utils/touch';
import { import {
@ -17,6 +19,7 @@ class Touchable extends React.Component {
static propTypes = { static propTypes = {
type: PropTypes.string.isRequired, type: PropTypes.string.isRequired,
onPress: PropTypes.func, onPress: PropTypes.func,
onLongPress: PropTypes.func,
testID: PropTypes.string, testID: PropTypes.string,
width: PropTypes.number, width: PropTypes.number,
favorite: PropTypes.bool, favorite: PropTypes.bool,
@ -59,6 +62,12 @@ class Touchable extends React.Component {
} }
} }
onLongPressHandlerStateChange = ({ nativeEvent }) => {
if (nativeEvent.state === State.ACTIVE) {
this.onLongPress();
}
}
_handleRelease = (nativeEvent) => { _handleRelease = (nativeEvent) => {
const { translationX } = nativeEvent; const { translationX } = nativeEvent;
@ -203,54 +212,70 @@ class Touchable extends React.Component {
} }
}; };
onLongPress = () => {
const { rowState } = this.state;
const { onLongPress } = this.props;
if (rowState !== 0) {
this.close();
return;
}
if (onLongPress) {
onLongPress();
}
};
render() { render() {
const { const {
testID, isRead, width, favorite, children, theme, isFocused, swipeEnabled testID, isRead, width, favorite, children, theme, isFocused, swipeEnabled
} = this.props; } = this.props;
return ( return (
<LongPressGestureHandler onHandlerStateChange={this.onLongPressHandlerStateChange}>
<PanGestureHandler
minDeltaX={20}
onGestureEvent={this._onGestureEvent}
onHandlerStateChange={this._onHandlerStateChange}
enabled={swipeEnabled}
>
<Animated.View> <Animated.View>
<LeftActions <PanGestureHandler
transX={this.transXReverse} minDeltaX={20}
isRead={isRead} onGestureEvent={this._onGestureEvent}
width={width} onHandlerStateChange={this._onHandlerStateChange}
onToggleReadPress={this.onToggleReadPress} enabled={swipeEnabled}
theme={theme}
/>
<RightActions
transX={this.transXReverse}
favorite={favorite}
width={width}
toggleFav={this.toggleFav}
onHidePress={this.onHidePress}
theme={theme}
/>
<Animated.View
style={{
transform: [{ translateX: this.transX }]
}}
> >
<Touch <Animated.View>
onPress={this.onPress} <LeftActions
theme={theme} transX={this.transXReverse}
testID={testID} isRead={isRead}
style={{ width={width}
backgroundColor: isFocused ? themes[theme].chatComponentBackground : themes[theme].backgroundColor onToggleReadPress={this.onToggleReadPress}
}} theme={theme}
> />
{children} <RightActions
</Touch> transX={this.transXReverse}
</Animated.View> favorite={favorite}
</Animated.View> width={width}
toggleFav={this.toggleFav}
onHidePress={this.onHidePress}
theme={theme}
/>
<Animated.View
style={{
transform: [{ translateX: this.transX }]
}}
>
<Touch
onPress={this.onPress}
theme={theme}
testID={testID}
style={{
backgroundColor: isFocused ? themes[theme].chatComponentBackground : themes[theme].backgroundColor
}}
>
{children}
</Touch>
</Animated.View>
</Animated.View>
</PanGestureHandler> </PanGestureHandler>
</Animated.View>
</LongPressGestureHandler>
); );
} }
} }

View File

@ -16,7 +16,8 @@ const attrs = [
'theme', 'theme',
'isFocused', 'isFocused',
'forceUpdate', 'forceUpdate',
'showLastMessage' 'showLastMessage',
'autoJoin'
]; ];
class RoomItemContainer extends React.Component { class RoomItemContainer extends React.Component {
@ -25,6 +26,7 @@ class RoomItemContainer extends React.Component {
showLastMessage: PropTypes.bool, showLastMessage: PropTypes.bool,
id: PropTypes.string, id: PropTypes.string,
onPress: PropTypes.func, onPress: PropTypes.func,
onLongPress: PropTypes.func,
username: PropTypes.string, username: PropTypes.string,
avatarSize: PropTypes.number, avatarSize: PropTypes.number,
width: PropTypes.number, width: PropTypes.number,
@ -41,7 +43,8 @@ class RoomItemContainer extends React.Component {
getRoomAvatar: PropTypes.func, getRoomAvatar: PropTypes.func,
getIsGroupChat: PropTypes.func, getIsGroupChat: PropTypes.func,
getIsRead: PropTypes.func, getIsRead: PropTypes.func,
swipeEnabled: PropTypes.bool swipeEnabled: PropTypes.bool,
autoJoin: PropTypes.bool
}; };
static defaultProps = { static defaultProps = {
@ -112,6 +115,13 @@ class RoomItemContainer extends React.Component {
return onPress(item); return onPress(item);
} }
onLongPress = () => {
const { item, onLongPress } = this.props;
if (onLongPress) {
return onLongPress(item);
}
}
render() { render() {
const { const {
item, item,
@ -129,7 +139,8 @@ class RoomItemContainer extends React.Component {
showLastMessage, showLastMessage,
username, username,
useRealName, useRealName,
swipeEnabled swipeEnabled,
autoJoin
} = this.props; } = this.props;
const name = getRoomTitle(item); const name = getRoomTitle(item);
const testID = `rooms-list-view-item-${ name }`; const testID = `rooms-list-view-item-${ name }`;
@ -160,6 +171,7 @@ class RoomItemContainer extends React.Component {
isGroupChat={this.isGroupChat} isGroupChat={this.isGroupChat}
isRead={isRead} isRead={isRead}
onPress={this.onPress} onPress={this.onPress}
onLongPress={this.onLongPress}
date={date} date={date}
accessibilityLabel={accessibilityLabel} accessibilityLabel={accessibilityLabel}
width={width} width={width}
@ -189,6 +201,7 @@ class RoomItemContainer extends React.Component {
tunreadGroup={item.tunreadGroup} tunreadGroup={item.tunreadGroup}
swipeEnabled={swipeEnabled} swipeEnabled={swipeEnabled}
teamMain={item.teamMain} teamMain={item.teamMain}
autoJoin={autoJoin}
/> />
); );
} }

View File

@ -96,5 +96,16 @@ export default StyleSheet.create({
height: '100%', height: '100%',
alignItems: 'center', alignItems: 'center',
justifyContent: 'center' justifyContent: 'center'
},
tagContainer: {
alignSelf: 'center',
alignItems: 'center',
borderRadius: 4,
marginHorizontal: 4
},
tagText: {
fontSize: 13,
paddingHorizontal: 4,
...sharedStyles.textSemibold
} }
}); });

View File

@ -22,7 +22,7 @@ export default function(state = initialState, action) {
case ROOM.LEAVE: case ROOM.LEAVE:
return { return {
...state, ...state,
rid: action.rid, rid: action.room.rid,
isDeleting: true isDeleting: true
}; };
case ROOM.DELETE: case ROOM.DELETE:

View File

@ -21,6 +21,10 @@ const createGroupChat = function createGroupChat() {
return RocketChat.createGroupChat(); return RocketChat.createGroupChat();
}; };
const createTeam = function createTeam(data) {
return RocketChat.createTeam(data);
};
const handleRequest = function* handleRequest({ data }) { const handleRequest = function* handleRequest({ data }) {
try { try {
const auth = yield select(state => state.login.isAuthenticated); const auth = yield select(state => state.login.isAuthenticated);
@ -29,11 +33,33 @@ const handleRequest = function* handleRequest({ data }) {
} }
let sub; let sub;
if (data.group) { if (data.isTeam) {
const {
type,
readOnly,
broadcast,
encrypted
} = data;
logEvent(events.CT_CREATE, {
type,
readOnly,
broadcast,
encrypted
});
const result = yield call(createTeam, data);
sub = {
rid: result?.team?.roomId,
...result.team,
t: result.team.type ? 'p' : 'c'
};
} else if (data.group) {
logEvent(events.SELECTED_USERS_CREATE_GROUP); logEvent(events.SELECTED_USERS_CREATE_GROUP);
const result = yield call(createGroupChat); const result = yield call(createGroupChat);
if (result.success) { if (result.success) {
({ room: sub } = result); sub = {
rid: result.room?._id,
...result.room
};
} }
} else { } else {
const { const {
@ -48,9 +74,13 @@ const handleRequest = function* handleRequest({ data }) {
broadcast, broadcast,
encrypted encrypted
}); });
sub = yield call(createChannel, data); const result = yield call(createChannel, data);
sub = {
rid: result?.channel?._id || result?.group?._id,
...result?.channel,
...result?.group
};
} }
try { try {
const db = database.active; const db = database.active;
const subCollection = db.get('subscriptions'); const subCollection = db.get('subscriptions');
@ -63,11 +93,10 @@ const handleRequest = function* handleRequest({ data }) {
} catch { } catch {
// do nothing // do nothing
} }
yield put(createChannelSuccess(sub)); yield put(createChannelSuccess(sub));
} catch (err) { } catch (err) {
logEvent(events[data.group ? 'SELECTED_USERS_CREATE_GROUP_F' : 'CR_CREATE_F']); logEvent(events[data.group ? 'SELECTED_USERS_CREATE_GROUP_F' : 'CR_CREATE_F']);
yield put(createChannelFailure(err)); yield put(createChannelFailure(err, data.isTeam));
} }
}; };
@ -79,10 +108,10 @@ const handleSuccess = function* handleSuccess({ data }) {
goRoom({ item: data, isMasterDetail }); goRoom({ item: data, isMasterDetail });
}; };
const handleFailure = function handleFailure({ err }) { const handleFailure = function handleFailure({ err, isTeam }) {
setTimeout(() => { setTimeout(() => {
const msg = err.reason || I18n.t('There_was_an_error_while_action', { action: I18n.t('creating_channel') }); const msg = err.data.errorType ? I18n.t(err.data.errorType, { room_name: err.data.details.channel_name }) : err.reason || I18n.t('There_was_an_error_while_action', { action: isTeam ? I18n.t('creating_team') : I18n.t('creating_channel') });
showErrorAlert(msg); showErrorAlert(msg, isTeam ? I18n.t('Create_Team') : I18n.t('Create_Channel'));
}, 300); }, 300);
}; };

View File

@ -16,6 +16,7 @@ import {
import { localAuthenticate } from '../utils/localAuthentication'; import { localAuthenticate } from '../utils/localAuthentication';
import { goRoom } from '../utils/goRoom'; import { goRoom } from '../utils/goRoom';
import { loginRequest } from '../actions/login'; import { loginRequest } from '../actions/login';
import log from '../utils/log';
const roomTypes = { const roomTypes = {
channel: 'c', direct: 'd', group: 'p', channels: 'l' channel: 'c', direct: 'd', group: 'p', channels: 'l'
@ -60,18 +61,19 @@ const navigate = function* navigate({ params }) {
const isMasterDetail = yield select(state => state.app.isMasterDetail); const isMasterDetail = yield select(state => state.app.isMasterDetail);
const focusedRooms = yield select(state => state.room.rooms); const focusedRooms = yield select(state => state.room.rooms);
const jumpToMessageId = params.messageId;
if (focusedRooms.includes(room.rid)) { if (focusedRooms.includes(room.rid)) {
// if there's one room on the list or last room is the one // if there's one room on the list or last room is the one
if (focusedRooms.length === 1 || focusedRooms[0] === room.rid) { if (focusedRooms.length === 1 || focusedRooms[0] === room.rid) {
yield goRoom({ item, isMasterDetail }); yield goRoom({ item, isMasterDetail, jumpToMessageId });
} else { } else {
popToRoot({ isMasterDetail }); popToRoot({ isMasterDetail });
yield goRoom({ item, isMasterDetail }); yield goRoom({ item, isMasterDetail, jumpToMessageId });
} }
} else { } else {
popToRoot({ isMasterDetail }); popToRoot({ isMasterDetail });
yield goRoom({ item, isMasterDetail }); yield goRoom({ item, isMasterDetail, jumpToMessageId });
} }
if (params.isCall) { if (params.isCall) {
@ -92,6 +94,15 @@ const fallbackNavigation = function* fallbackNavigation() {
yield put(appInit()); yield put(appInit());
}; };
const handleOAuth = function* handleOAuth({ params }) {
const { credentialToken, credentialSecret } = params;
try {
yield RocketChat.loginOAuthOrSso({ oauth: { credentialToken, credentialSecret } });
} catch (e) {
log(e);
}
};
const handleOpen = function* handleOpen({ params }) { const handleOpen = function* handleOpen({ params }) {
const serversDB = database.servers; const serversDB = database.servers;
const serversCollection = serversDB.get('servers'); const serversCollection = serversDB.get('servers');
@ -107,6 +118,11 @@ const handleOpen = function* handleOpen({ params }) {
}); });
} }
if (params.type === 'oauth') {
yield handleOAuth({ params });
return;
}
// If there's no host on the deep link params and the app is opened, just call appInit() // If there's no host on the deep link params and the app is opened, just call appInit()
if (!host) { if (!host) {
yield fallbackNavigation(); yield fallbackNavigation();

View File

@ -4,6 +4,7 @@ import {
takeLatest, take, select, delay, race, put takeLatest, take, select, delay, race, put
} from 'redux-saga/effects'; } from 'redux-saga/effects';
import EventEmitter from '../utils/events';
import Navigation from '../lib/Navigation'; import Navigation from '../lib/Navigation';
import * as types from '../actions/actionsTypes'; import * as types from '../actions/actionsTypes';
import { removedRoom } from '../actions/room'; import { removedRoom } from '../actions/room';
@ -11,6 +12,7 @@ import RocketChat from '../lib/rocketchat';
import log, { logEvent, events } from '../utils/log'; import log, { logEvent, events } from '../utils/log';
import I18n from '../i18n'; import I18n from '../i18n';
import { showErrorAlert } from '../utils/info'; import { showErrorAlert } from '../utils/info';
import { LISTENER } from '../containers/Toast';
const watchUserTyping = function* watchUserTyping({ rid, status }) { const watchUserTyping = function* watchUserTyping({ rid, status }) {
const auth = yield select(state => state.login.isAuthenticated); const auth = yield select(state => state.login.isAuthenticated);
@ -30,13 +32,18 @@ const watchUserTyping = function* watchUserTyping({ rid, status }) {
} }
}; };
const handleRemovedRoom = function* handleRemovedRoom() { const handleRemovedRoom = function* handleRemovedRoom(roomType) {
const isMasterDetail = yield select(state => state.app.isMasterDetail); const isMasterDetail = yield select(state => state.app.isMasterDetail);
if (isMasterDetail) { if (isMasterDetail) {
yield Navigation.navigate('DrawerNavigator'); yield Navigation.navigate('DrawerNavigator');
} else { } else {
yield Navigation.navigate('RoomsListView'); yield Navigation.navigate('RoomsListView');
} }
if (roomType === 'team') {
EventEmitter.emit(LISTENER, { message: I18n.t('Left_The_Team_Successfully') });
}
// types.ROOM.REMOVE is triggered by `subscriptions-changed` with `removed` arg // types.ROOM.REMOVE is triggered by `subscriptions-changed` with `removed` arg
const { timeout } = yield race({ const { timeout } = yield race({
deleteFinished: take(types.ROOM.REMOVED), deleteFinished: take(types.ROOM.REMOVED),
@ -47,17 +54,26 @@ const handleRemovedRoom = function* handleRemovedRoom() {
} }
}; };
const handleLeaveRoom = function* handleLeaveRoom({ rid, t }) { const handleLeaveRoom = function* handleLeaveRoom({ room, roomType, selected }) {
logEvent(events.RA_LEAVE); logEvent(events.RA_LEAVE);
try { try {
const result = yield RocketChat.leaveRoom(rid, t); let result = {};
if (result.success) {
yield handleRemovedRoom(); if (roomType === 'channel') {
result = yield RocketChat.leaveRoom(room.rid, room.t);
} else if (roomType === 'team') {
result = yield RocketChat.leaveTeam({ teamName: room.name, ...(selected && { rooms: selected }) });
}
if (result?.success) {
yield handleRemovedRoom(roomType);
} }
} catch (e) { } catch (e) {
logEvent(events.RA_LEAVE_F); logEvent(events.RA_LEAVE_F);
if (e.data && e.data.errorType === 'error-you-are-last-owner') { if (e.data && e.data.errorType === 'error-you-are-last-owner') {
Alert.alert(I18n.t('Oops'), I18n.t(e.data.errorType)); Alert.alert(I18n.t('Oops'), I18n.t(e.data.errorType));
} else if (e?.data?.error === 'last-owner-can-not-be-removed') {
Alert.alert(I18n.t('Oops'), I18n.t(e.data.error));
} else { } else {
Alert.alert(I18n.t('Oops'), I18n.t('There_was_an_error_while_action', { action: I18n.t('leaving_room') })); Alert.alert(I18n.t('Oops'), I18n.t('There_was_an_error_while_action', { action: I18n.t('leaving_room') }));
} }

View File

@ -71,6 +71,9 @@ import ShareView from '../views/ShareView';
import CreateDiscussionView from '../views/CreateDiscussionView'; import CreateDiscussionView from '../views/CreateDiscussionView';
import QueueListView from '../ee/omnichannel/views/QueueListView'; import QueueListView from '../ee/omnichannel/views/QueueListView';
import AddChannelTeamView from '../views/AddChannelTeamView';
import AddExistingChannelView from '../views/AddExistingChannelView';
import SelectListView from '../views/SelectListView';
// ChatsStackNavigator // ChatsStackNavigator
const ChatsStack = createStackNavigator(); const ChatsStack = createStackNavigator();
@ -91,6 +94,11 @@ const ChatsStackNavigator = () => {
component={RoomActionsView} component={RoomActionsView}
options={RoomActionsView.navigationOptions} options={RoomActionsView.navigationOptions}
/> />
<ChatsStack.Screen
name='SelectListView'
component={SelectListView}
options={SelectListView.navigationOptions}
/>
<ChatsStack.Screen <ChatsStack.Screen
name='RoomInfoView' name='RoomInfoView'
component={RoomInfoView} component={RoomInfoView}
@ -174,6 +182,21 @@ const ChatsStackNavigator = () => {
component={TeamChannelsView} component={TeamChannelsView}
options={TeamChannelsView.navigationOptions} options={TeamChannelsView.navigationOptions}
/> />
<ChatsStack.Screen
name='CreateChannelView'
component={CreateChannelView}
options={CreateChannelView.navigationOptions}
/>
<ChatsStack.Screen
name='AddChannelTeamView'
component={AddChannelTeamView}
options={AddChannelTeamView.navigationOptions}
/>
<ChatsStack.Screen
name='AddExistingChannelView'
component={AddExistingChannelView}
options={AddExistingChannelView.navigationOptions}
/>
<ChatsStack.Screen <ChatsStack.Screen
name='MarkdownTableView' name='MarkdownTableView'
component={MarkdownTableView} component={MarkdownTableView}

View File

@ -61,6 +61,9 @@ import { setKeyCommands, deleteKeyCommands } from '../../commands';
import ShareView from '../../views/ShareView'; import ShareView from '../../views/ShareView';
import QueueListView from '../../ee/omnichannel/views/QueueListView'; import QueueListView from '../../ee/omnichannel/views/QueueListView';
import AddChannelTeamView from '../../views/AddChannelTeamView';
import AddExistingChannelView from '../../views/AddExistingChannelView';
import SelectListView from '../../views/SelectListView';
// ChatsStackNavigator // ChatsStackNavigator
const ChatsStack = createStackNavigator(); const ChatsStack = createStackNavigator();
@ -117,6 +120,11 @@ const ModalStackNavigator = React.memo(({ navigation }) => {
component={RoomInfoView} component={RoomInfoView}
options={RoomInfoView.navigationOptions} options={RoomInfoView.navigationOptions}
/> />
<ModalStack.Screen
name='SelectListView'
component={SelectListView}
options={SelectListView.navigationOptions}
/>
<ModalStack.Screen <ModalStack.Screen
name='RoomInfoEditView' name='RoomInfoEditView'
component={RoomInfoEditView} component={RoomInfoEditView}
@ -141,6 +149,16 @@ const ModalStackNavigator = React.memo(({ navigation }) => {
component={InviteUsersView} component={InviteUsersView}
options={InviteUsersView.navigationOptions} options={InviteUsersView.navigationOptions}
/> />
<ModalStack.Screen
name='AddChannelTeamView'
component={AddChannelTeamView}
options={AddChannelTeamView.navigationOptions}
/>
<ModalStack.Screen
name='AddExistingChannelView'
component={AddExistingChannelView}
options={AddExistingChannelView.navigationOptions}
/>
<ModalStack.Screen <ModalStack.Screen
name='InviteUsersEditView' name='InviteUsersEditView'
component={InviteUsersEditView} component={InviteUsersEditView}

View File

@ -14,7 +14,6 @@ const navigate = ({ item, isMasterDetail, ...props }) => {
t: item.t, t: item.t,
prid: item.prid, prid: item.prid,
room: item, room: item,
search: item.search,
visitor: item.visitor, visitor: item.visitor,
roomUserId: RocketChat.getUidDirectMessage(item), roomUserId: RocketChat.getUidDirectMessage(item),
...props ...props

View File

@ -88,6 +88,7 @@ export default {
// NEW MESSAGE VIEW // NEW MESSAGE VIEW
NEW_MSG_CREATE_CHANNEL: 'new_msg_create_channel', NEW_MSG_CREATE_CHANNEL: 'new_msg_create_channel',
NEW_MSG_CREATE_TEAM: 'new_msg_create_team',
NEW_MSG_CREATE_GROUP_CHAT: 'new_msg_create_group_chat', NEW_MSG_CREATE_GROUP_CHAT: 'new_msg_create_group_chat',
NEW_MSG_CREATE_DISCUSSION: 'new_msg_create_discussion', NEW_MSG_CREATE_DISCUSSION: 'new_msg_create_discussion',
NEW_MSG_CHAT_WITH_USER: 'new_msg_chat_with_user', NEW_MSG_CHAT_WITH_USER: 'new_msg_chat_with_user',
@ -98,14 +99,22 @@ export default {
SELECTED_USERS_CREATE_GROUP: 'selected_users_create_group', SELECTED_USERS_CREATE_GROUP: 'selected_users_create_group',
SELECTED_USERS_CREATE_GROUP_F: 'selected_users_create_group_f', SELECTED_USERS_CREATE_GROUP_F: 'selected_users_create_group_f',
// ADD EXISTING CHANNEL VIEW
AEC_ADD_CHANNEL: 'aec_add_channel',
AEC_REMOVE_CHANNEL: 'aec_remove_channel',
// CREATE CHANNEL VIEW // CREATE CHANNEL VIEW
CR_CREATE: 'cr_create', CR_CREATE: 'cr_create',
CT_CREATE: 'ct_create',
CR_CREATE_F: 'cr_create_f', CR_CREATE_F: 'cr_create_f',
CT_CREATE_F: 'ct_create_f',
CR_TOGGLE_TYPE: 'cr_toggle_type', CR_TOGGLE_TYPE: 'cr_toggle_type',
CR_TOGGLE_READ_ONLY: 'cr_toggle_read_only', CR_TOGGLE_READ_ONLY: 'cr_toggle_read_only',
CR_TOGGLE_BROADCAST: 'cr_toggle_broadcast', CR_TOGGLE_BROADCAST: 'cr_toggle_broadcast',
CR_TOGGLE_ENCRYPTED: 'cr_toggle_encrypted', CR_TOGGLE_ENCRYPTED: 'cr_toggle_encrypted',
CR_REMOVE_USER: 'cr_remove_user', CR_REMOVE_USER: 'cr_remove_user',
CT_ADD_ROOM_TO_TEAM: 'ct_add_room_to_team',
CT_ADD_ROOM_TO_TEAM_F: 'ct_add_room_to_team_f',
// CREATE DISCUSSION VIEW // CREATE DISCUSSION VIEW
CD_CREATE: 'cd_create', CD_CREATE: 'cd_create',
@ -246,6 +255,13 @@ export default {
RA_TOGGLE_BLOCK_USER_F: 'ra_toggle_block_user_f', RA_TOGGLE_BLOCK_USER_F: 'ra_toggle_block_user_f',
RA_TOGGLE_ENCRYPTED: 'ra_toggle_encrypted', RA_TOGGLE_ENCRYPTED: 'ra_toggle_encrypted',
RA_TOGGLE_ENCRYPTED_F: 'ra_toggle_encrypted_f', RA_TOGGLE_ENCRYPTED_F: 'ra_toggle_encrypted_f',
RA_LEAVE_TEAM: 'ra_leave_team',
RA_LEAVE_TEAM_F: 'ra_leave_team_f',
RA_CONVERT_TO_TEAM: 'ra_convert_to_team',
RA_CONVERT_TO_TEAM_F: 'ra_convert_to_team_f',
RA_MOVE_TO_TEAM: 'ra_move_to_team',
RA_MOVE_TO_TEAM_F: 'ra_move_to_team_f',
RA_SEARCH_TEAM: 'ra_search_team',
// ROOM INFO VIEW // ROOM INFO VIEW
RI_GO_RI_EDIT: 'ri_go_ri_edit', RI_GO_RI_EDIT: 'ri_go_ri_edit',
@ -265,6 +281,8 @@ export default {
RI_EDIT_TOGGLE_ARCHIVE_F: 'ri_edit_toggle_archive_f', RI_EDIT_TOGGLE_ARCHIVE_F: 'ri_edit_toggle_archive_f',
RI_EDIT_DELETE: 'ri_edit_delete', RI_EDIT_DELETE: 'ri_edit_delete',
RI_EDIT_DELETE_F: 'ri_edit_delete_f', RI_EDIT_DELETE_F: 'ri_edit_delete_f',
RI_EDIT_DELETE_TEAM: 'ri_edit_delete_team',
RI_EDIT_DELETE_TEAM_F: 'ri_edit_delete_team_f',
// JITSI MEET VIEW // JITSI MEET VIEW
JM_CONFERENCE_JOIN: 'jm_conference_join', JM_CONFERENCE_JOIN: 'jm_conference_join',
@ -318,5 +336,9 @@ export default {
TC_SEARCH: 'tc_search', TC_SEARCH: 'tc_search',
TC_CANCEL_SEARCH: 'tc_cancel_search', TC_CANCEL_SEARCH: 'tc_cancel_search',
TC_GO_ACTIONS: 'tc_go_actions', TC_GO_ACTIONS: 'tc_go_actions',
TC_GO_ROOM: 'tc_go_room' TC_GO_ROOM: 'tc_go_room',
TC_DELETE_ROOM: 'tc_delete_room',
TC_DELETE_ROOM_F: 'tc_delete_room_f',
TC_TOGGLE_AUTOJOIN: 'tc_toggle_autojoin',
TC_TOGGLE_AUTOJOIN_F: 'tc_toggle_autojoin_f'
}; };

View File

@ -27,10 +27,10 @@ export const MessageTypeValues = [
value: 'rm', value: 'rm',
text: 'Message_HideType_rm' text: 'Message_HideType_rm'
}, { }, {
value: 'subscription_role_added', value: 'subscription-role-added',
text: 'Message_HideType_subscription_role_added' text: 'Message_HideType_subscription_role_added'
}, { }, {
value: 'subscription_role_removed', value: 'subscription-role-removed',
text: 'Message_HideType_subscription_role_removed' text: 'Message_HideType_subscription_role_removed'
}, { }, {
value: 'room_archived', value: 'room_archived',

View File

@ -45,3 +45,5 @@ export const getBadgeColor = ({ subscription, messageId, theme }) => {
}; };
export const makeThreadName = messageRecord => messageRecord.msg || messageRecord?.attachments[0]?.title; export const makeThreadName = messageRecord => messageRecord.msg || messageRecord?.attachments[0]?.title;
export const isTeamRoom = ({ teamId, joined }) => teamId && joined;

View File

@ -0,0 +1,75 @@
import React, { useEffect } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import * as List from '../containers/List';
import StatusBar from '../containers/StatusBar';
import { useTheme } from '../theme';
import * as HeaderButton from '../containers/HeaderButton';
import SafeAreaView from '../containers/SafeAreaView';
import I18n from '../i18n';
const setHeader = (navigation, isMasterDetail) => {
const options = {
headerTitle: I18n.t('Add_Channel_to_Team')
};
if (isMasterDetail) {
options.headerLeft = () => <HeaderButton.CloseModal navigation={navigation} />;
}
navigation.setOptions(options);
};
const AddChannelTeamView = ({
navigation, route, isMasterDetail
}) => {
const { teamId, teamChannels } = route.params;
const { theme } = useTheme();
useEffect(() => {
setHeader(navigation, isMasterDetail);
}, []);
return (
<SafeAreaView testID='add-channel-team-view'>
<StatusBar />
<List.Container>
<List.Separator />
<List.Item
title='Create_New'
onPress={() => (isMasterDetail
? navigation.navigate('SelectedUsersViewCreateChannel', { nextAction: () => navigation.navigate('CreateChannelView', { teamId }) })
: navigation.navigate('SelectedUsersView', { nextAction: () => navigation.navigate('ChatsStackNavigator', { screen: 'CreateChannelView', params: { teamId } }) }))
}
testID='add-channel-team-view-create-channel'
left={() => <List.Icon name='team' />}
right={() => <List.Icon name='chevron-right' />}
theme={theme}
/>
<List.Separator />
<List.Item
title='Add_Existing'
onPress={() => navigation.navigate('AddExistingChannelView', { teamId, teamChannels })}
testID='add-channel-team-view-add-existing'
left={() => <List.Icon name='channel-public' />}
right={() => <List.Icon name='chevron-right' />}
theme={theme}
/>
<List.Separator />
</List.Container>
</SafeAreaView>
);
};
AddChannelTeamView.propTypes = {
route: PropTypes.object,
navigation: PropTypes.object,
isMasterDetail: PropTypes.bool
};
const mapStateToProps = state => ({
isMasterDetail: state.app.isMasterDetail
});
export default connect(mapStateToProps)(AddChannelTeamView);

View File

@ -0,0 +1,215 @@
import React from 'react';
import PropTypes from 'prop-types';
import {
View, FlatList
} from 'react-native';
import { connect } from 'react-redux';
import { Q } from '@nozbe/watermelondb';
import * as List from '../containers/List';
import database from '../lib/database';
import RocketChat from '../lib/rocketchat';
import I18n from '../i18n';
import log, { events, logEvent } from '../utils/log';
import SearchBox from '../containers/SearchBox';
import * as HeaderButton from '../containers/HeaderButton';
import StatusBar from '../containers/StatusBar';
import { themes } from '../constants/colors';
import { withTheme } from '../theme';
import SafeAreaView from '../containers/SafeAreaView';
import Loading from '../containers/Loading';
import { animateNextTransition } from '../utils/layoutAnimation';
import { goRoom } from '../utils/goRoom';
import { showErrorAlert } from '../utils/info';
import debounce from '../utils/debounce';
const QUERY_SIZE = 50;
class AddExistingChannelView extends React.Component {
static propTypes = {
navigation: PropTypes.object,
route: PropTypes.object,
theme: PropTypes.string,
isMasterDetail: PropTypes.bool,
addTeamChannelPermission: PropTypes.array
};
constructor(props) {
super(props);
this.query();
this.teamId = props.route?.params?.teamId;
this.state = {
search: [],
channels: [],
selected: [],
loading: false
};
this.setHeader();
}
setHeader = () => {
const { navigation, isMasterDetail } = this.props;
const { selected } = this.state;
const options = {
headerTitle: I18n.t('Add_Existing_Channel')
};
if (isMasterDetail) {
options.headerLeft = () => <HeaderButton.CloseModal navigation={navigation} />;
}
options.headerRight = () => selected.length > 0 && (
<HeaderButton.Container>
<HeaderButton.Item title={I18n.t('Next')} onPress={this.submit} testID='add-existing-channel-view-submit' />
</HeaderButton.Container>
);
navigation.setOptions(options);
}
query = async(stringToSearch = '') => {
try {
const { addTeamChannelPermission } = this.props;
const db = database.active;
const channels = await db.collections
.get('subscriptions')
.query(
Q.where('team_id', ''),
Q.where('t', Q.oneOf(['c', 'p'])),
Q.where('name', Q.like(`%${ stringToSearch }%`)),
Q.experimentalTake(QUERY_SIZE),
Q.experimentalSortBy('room_updated_at', Q.desc)
)
.fetch();
const asyncFilter = async(channelsArray) => {
const results = await Promise.all(channelsArray.map(async(channel) => {
if (channel.prid) {
return false;
}
const permissions = await RocketChat.hasPermission([addTeamChannelPermission], channel.rid);
if (!permissions[0]) {
return false;
}
return true;
}));
return channelsArray.filter((_v, index) => results[index]);
};
const channelFiltered = await asyncFilter(channels);
this.setState({ channels: channelFiltered });
} catch (e) {
log(e);
}
}
onSearchChangeText = debounce((text) => {
this.query(text);
}, 300)
dismiss = () => {
const { navigation } = this.props;
return navigation.pop();
}
submit = async() => {
const { selected } = this.state;
const { isMasterDetail } = this.props;
this.setState({ loading: true });
try {
logEvent(events.CT_ADD_ROOM_TO_TEAM);
const result = await RocketChat.addRoomsToTeam({ rooms: selected, teamId: this.teamId });
if (result.success) {
this.setState({ loading: false });
goRoom({ item: result, isMasterDetail });
}
} catch (e) {
logEvent(events.CT_ADD_ROOM_TO_TEAM_F);
showErrorAlert(I18n.t(e.data.error), I18n.t('Add_Existing_Channel'), () => {});
this.setState({ loading: false });
}
}
renderHeader = () => {
const { theme } = this.props;
return (
<View style={{ backgroundColor: themes[theme].auxiliaryBackground }}>
<SearchBox onChangeText={text => this.onSearchChangeText(text)} testID='add-existing-channel-view-search' />
</View>
);
}
isChecked = (rid) => {
const { selected } = this.state;
return selected.includes(rid);
}
toggleChannel = (rid) => {
const { selected } = this.state;
animateNextTransition();
if (!this.isChecked(rid)) {
logEvent(events.AEC_ADD_CHANNEL);
this.setState({ selected: [...selected, rid] }, () => this.setHeader());
} else {
logEvent(events.AEC_REMOVE_CHANNEL);
const filterSelected = selected.filter(el => el !== rid);
this.setState({ selected: filterSelected }, () => this.setHeader());
}
}
renderItem = ({ item }) => {
const isChecked = this.isChecked(item.rid);
// TODO: reuse logic inside RoomTypeIcon
const icon = item.t === 'p' && !item.teamId ? 'channel-private' : 'channel-public';
return (
<List.Item
title={RocketChat.getRoomTitle(item)}
translateTitle={false}
onPress={() => this.toggleChannel(item.rid)}
testID={`add-existing-channel-view-item-${ item.name }`}
left={() => <List.Icon name={icon} />}
right={() => (isChecked ? <List.Icon name='check' /> : null)}
/>
);
}
renderList = () => {
const { search, channels } = this.state;
const { theme } = this.props;
return (
<FlatList
data={search.length > 0 ? search : channels}
extraData={this.state}
keyExtractor={item => item._id}
ListHeaderComponent={this.renderHeader}
renderItem={this.renderItem}
ItemSeparatorComponent={List.Separator}
contentContainerStyle={{ backgroundColor: themes[theme].backgroundColor }}
keyboardShouldPersistTaps='always'
/>
);
}
render() {
const { loading } = this.state;
return (
<SafeAreaView testID='add-existing-channel-view'>
<StatusBar />
{this.renderList()}
<Loading visible={loading} />
</SafeAreaView>
);
}
}
const mapStateToProps = state => ({
isMasterDetail: state.app.isMasterDetail,
addTeamChannelPermission: state.permissions['add-team-channel']
});
export default connect(mapStateToProps)(withTheme(AddExistingChannelView));

View File

@ -68,12 +68,9 @@ const styles = StyleSheet.create({
}); });
class CreateChannelView extends React.Component { class CreateChannelView extends React.Component {
static navigationOptions = () => ({
title: I18n.t('Create_Channel')
});
static propTypes = { static propTypes = {
navigation: PropTypes.object, navigation: PropTypes.object,
route: PropTypes.object,
baseUrl: PropTypes.string, baseUrl: PropTypes.string,
create: PropTypes.func.isRequired, create: PropTypes.func.isRequired,
removeUser: PropTypes.func.isRequired, removeUser: PropTypes.func.isRequired,
@ -86,15 +83,24 @@ class CreateChannelView extends React.Component {
id: PropTypes.string, id: PropTypes.string,
token: PropTypes.string token: PropTypes.string
}), }),
theme: PropTypes.string theme: PropTypes.string,
teamId: PropTypes.string
}; };
state = { constructor(props) {
channelName: '', super(props);
type: true, const { route } = this.props;
readOnly: false, const isTeam = route?.params?.isTeam || false;
encrypted: false, this.teamId = route?.params?.teamId;
broadcast: false this.state = {
channelName: '',
type: true,
readOnly: false,
encrypted: false,
broadcast: false,
isTeam
};
this.setHeader();
} }
shouldComponentUpdate(nextProps, nextState) { shouldComponentUpdate(nextProps, nextState) {
@ -134,6 +140,15 @@ class CreateChannelView extends React.Component {
return false; return false;
} }
setHeader = () => {
const { navigation } = this.props;
const { isTeam } = this.state;
navigation.setOptions({
title: isTeam ? I18n.t('Create_Team') : I18n.t('Create_Channel')
});
}
toggleRightButton = (channelName) => { toggleRightButton = (channelName) => {
const { navigation } = this.props; const { navigation } = this.props;
navigation.setOptions({ navigation.setOptions({
@ -152,9 +167,11 @@ class CreateChannelView extends React.Component {
submit = () => { submit = () => {
const { const {
channelName, type, readOnly, broadcast, encrypted channelName, type, readOnly, broadcast, encrypted, isTeam
} = this.state; } = this.state;
const { users: usersProps, isFetching, create } = this.props; const {
users: usersProps, isFetching, create
} = this.props;
if (!channelName.trim() || isFetching) { if (!channelName.trim() || isFetching) {
return; return;
@ -163,9 +180,9 @@ class CreateChannelView extends React.Component {
// transform users object into array of usernames // transform users object into array of usernames
const users = usersProps.map(user => user.name); const users = usersProps.map(user => user.name);
// create channel // create channel or team
create({ create({
name: channelName, users, type, readOnly, broadcast, encrypted name: channelName, users, type, readOnly, broadcast, encrypted, isTeam, teamId: this.teamId
}); });
Review.pushPositiveEvent(); Review.pushPositiveEvent();
@ -196,11 +213,12 @@ class CreateChannelView extends React.Component {
} }
renderType() { renderType() {
const { type } = this.state; const { type, isTeam } = this.state;
return this.renderSwitch({ return this.renderSwitch({
id: 'type', id: 'type',
value: type, value: type,
label: 'Private_Channel', label: isTeam ? 'Private_Team' : 'Private_Channel',
onValueChange: (value) => { onValueChange: (value) => {
logEvent(events.CR_TOGGLE_TYPE); logEvent(events.CR_TOGGLE_TYPE);
// If we set the channel as public, encrypted status should be false // If we set the channel as public, encrypted status should be false
@ -210,11 +228,12 @@ class CreateChannelView extends React.Component {
} }
renderReadOnly() { renderReadOnly() {
const { readOnly, broadcast } = this.state; const { readOnly, broadcast, isTeam } = this.state;
return this.renderSwitch({ return this.renderSwitch({
id: 'readonly', id: 'readonly',
value: readOnly, value: readOnly,
label: 'Read_Only_Channel', label: isTeam ? 'Read_Only_Team' : 'Read_Only_Channel',
onValueChange: (value) => { onValueChange: (value) => {
logEvent(events.CR_TOGGLE_READ_ONLY); logEvent(events.CR_TOGGLE_READ_ONLY);
this.setState({ readOnly: value }); this.setState({ readOnly: value });
@ -244,11 +263,12 @@ class CreateChannelView extends React.Component {
} }
renderBroadcast() { renderBroadcast() {
const { broadcast, readOnly } = this.state; const { broadcast, readOnly, isTeam } = this.state;
return this.renderSwitch({ return this.renderSwitch({
id: 'broadcast', id: 'broadcast',
value: broadcast, value: broadcast,
label: 'Broadcast_Channel', label: isTeam ? 'Broadcast_Team' : 'Broadcast_Channel',
onValueChange: (value) => { onValueChange: (value) => {
logEvent(events.CR_TOGGLE_BROADCAST); logEvent(events.CR_TOGGLE_BROADCAST);
this.setState({ this.setState({
@ -301,8 +321,10 @@ class CreateChannelView extends React.Component {
} }
render() { render() {
const { channelName } = this.state; const { channelName, isTeam } = this.state;
const { users, isFetching, theme } = this.props; const {
users, isFetching, theme
} = this.props;
const userCount = users.length; const userCount = users.length;
return ( return (
@ -318,10 +340,10 @@ class CreateChannelView extends React.Component {
<TextInput <TextInput
autoFocus autoFocus
style={[styles.input, { backgroundColor: themes[theme].backgroundColor }]} style={[styles.input, { backgroundColor: themes[theme].backgroundColor }]}
label={I18n.t('Channel_Name')} label={isTeam ? I18n.t('Team_Name') : I18n.t('Channel_Name')}
value={channelName} value={channelName}
onChangeText={this.onChangeText} onChangeText={this.onChangeText}
placeholder={I18n.t('Channel_Name')} placeholder={isTeam ? I18n.t('Team_Name') : I18n.t('Channel_Name')}
returnKeyType='done' returnKeyType='done'
testID='create-channel-name' testID='create-channel-name'
autoCorrect={false} autoCorrect={false}

View File

@ -64,6 +64,11 @@ export default class DirectoryOptions extends PureComponent {
icon = 'channel-public'; icon = 'channel-public';
} }
if (itemType === 'teams') {
text = 'Teams';
icon = 'teams';
}
return ( return (
<Touch <Touch
onPress={() => changeType(itemType)} onPress={() => changeType(itemType)}
@ -105,6 +110,7 @@ export default class DirectoryOptions extends PureComponent {
</Touch> </Touch>
{this.renderItem('channels')} {this.renderItem('channels')}
{this.renderItem('users')} {this.renderItem('users')}
{this.renderItem('teams')}
{isFederationEnabled {isFederationEnabled
? ( ? (
<> <>

View File

@ -121,6 +121,8 @@ class DirectoryView extends React.Component {
logEvent(events.DIRECTORY_SEARCH_USERS); logEvent(events.DIRECTORY_SEARCH_USERS);
} else if (type === 'channels') { } else if (type === 'channels') {
logEvent(events.DIRECTORY_SEARCH_CHANNELS); logEvent(events.DIRECTORY_SEARCH_CHANNELS);
} else if (type === 'teams') {
logEvent(events.DIRECTORY_SEARCH_TEAMS);
} }
} }
@ -149,10 +151,14 @@ class DirectoryView extends React.Component {
if (result.success) { if (result.success) {
this.goRoom({ rid: result.room._id, name: item.username, t: 'd' }); this.goRoom({ rid: result.room._id, name: item.username, t: 'd' });
} }
} else { } else if (['p', 'c'].includes(item.t) && !item.teamMain) {
const { room } = await RocketChat.getRoomInfo(item._id); const { room } = await RocketChat.getRoomInfo(item._id);
this.goRoom({ this.goRoom({
rid: item._id, name: item.name, joinCodeRequired: room.joinCodeRequired, t: 'c', search: true rid: item._id, name: item.name, joinCodeRequired: room.joinCodeRequired, t: item.t, search: true
});
} else {
this.goRoom({
rid: item._id, name: item.name, t: item.t, search: true, teamMain: item.teamMain, teamId: item.teamId
}); });
} }
} }
@ -160,6 +166,19 @@ class DirectoryView extends React.Component {
renderHeader = () => { renderHeader = () => {
const { type } = this.state; const { type } = this.state;
const { theme } = this.props; const { theme } = this.props;
let text = 'Users';
let icon = 'user';
if (type === 'channels') {
text = 'Channels';
icon = 'channel-public';
}
if (type === 'teams') {
text = 'Teams';
icon = 'teams';
}
return ( return (
<> <>
<SearchBox <SearchBox
@ -174,8 +193,8 @@ class DirectoryView extends React.Component {
theme={theme} theme={theme}
> >
<View style={[sharedStyles.separatorVertical, styles.toggleDropdownContainer, { borderColor: themes[theme].separatorColor }]}> <View style={[sharedStyles.separatorVertical, styles.toggleDropdownContainer, { borderColor: themes[theme].separatorColor }]}>
<CustomIcon style={[styles.toggleDropdownIcon, { color: themes[theme].tintColor }]} size={20} name={type === 'users' ? 'user' : 'channel-public'} /> <CustomIcon style={[styles.toggleDropdownIcon, { color: themes[theme].tintColor }]} size={20} name={icon} />
<Text style={[styles.toggleDropdownText, { color: themes[theme].tintColor }]}>{type === 'users' ? I18n.t('Users') : I18n.t('Channels')}</Text> <Text style={[styles.toggleDropdownText, { color: themes[theme].tintColor }]}>{I18n.t(text)}</Text>
<CustomIcon name='chevron-down' size={20} style={[styles.toggleDropdownArrow, { color: themes[theme].auxiliaryTintColor }]} /> <CustomIcon name='chevron-down' size={20} style={[styles.toggleDropdownArrow, { color: themes[theme].auxiliaryTintColor }]} />
</View> </View>
</Touch> </Touch>
@ -217,12 +236,25 @@ class DirectoryView extends React.Component {
/> />
); );
} }
if (type === 'teams') {
return (
<DirectoryItem
avatar={item.name}
description={item.name}
rightLabel={I18n.t('N_channels', { n: item.roomsCount })}
type={item.t}
teamMain={item.teamMain}
{...commonProps}
/>
);
}
return ( return (
<DirectoryItem <DirectoryItem
avatar={item.name} avatar={item.name}
description={item.topic} description={item.topic}
rightLabel={I18n.t('N_users', { n: item.usersCount })} rightLabel={I18n.t('N_users', { n: item.usersCount })}
type='c' type={item.t}
{...commonProps} {...commonProps}
/> />
); );

View File

@ -16,6 +16,7 @@ import { withTheme } from '../../theme';
import { getUserSelector } from '../../selectors/login'; import { getUserSelector } from '../../selectors/login';
import { withActionSheet } from '../../containers/ActionSheet'; import { withActionSheet } from '../../containers/ActionSheet';
import SafeAreaView from '../../containers/SafeAreaView'; import SafeAreaView from '../../containers/SafeAreaView';
import getThreadName from '../../lib/methods/getThreadName';
class MessagesView extends React.Component { class MessagesView extends React.Component {
static propTypes = { static propTypes = {
@ -26,7 +27,8 @@ class MessagesView extends React.Component {
customEmojis: PropTypes.object, customEmojis: PropTypes.object,
theme: PropTypes.string, theme: PropTypes.string,
showActionSheet: PropTypes.func, showActionSheet: PropTypes.func,
useRealName: PropTypes.bool useRealName: PropTypes.bool,
isMasterDetail: PropTypes.bool
} }
constructor(props) { constructor(props) {
@ -81,6 +83,32 @@ class MessagesView extends React.Component {
navigation.navigate('RoomInfoView', navParam); navigation.navigate('RoomInfoView', navParam);
} }
jumpToMessage = async({ item }) => {
const { navigation, isMasterDetail } = this.props;
let params = {
rid: this.rid,
jumpToMessageId: item._id,
t: this.t,
room: this.room
};
if (item.tmid) {
if (isMasterDetail) {
navigation.navigate('DrawerNavigator');
} else {
navigation.pop(2);
}
params = {
...params,
tmid: item.tmid,
name: await getThreadName(this.rid, item.tmid, item._id),
t: 'thread'
};
navigation.push('RoomView', params);
} else {
navigation.navigate('RoomView', params);
}
}
defineMessagesViewContent = (name) => { defineMessagesViewContent = (name) => {
const { const {
user, baseUrl, theme, useRealName user, baseUrl, theme, useRealName
@ -93,11 +121,13 @@ class MessagesView extends React.Component {
timeFormat: 'MMM Do YYYY, h:mm:ss a', timeFormat: 'MMM Do YYYY, h:mm:ss a',
isEdited: !!item.editedAt, isEdited: !!item.editedAt,
isHeader: true, isHeader: true,
isThreadRoom: true,
attachments: item.attachments || [], attachments: item.attachments || [],
useRealName, useRealName,
showAttachment: this.showAttachment, showAttachment: this.showAttachment,
getCustomEmoji: this.getCustomEmoji, getCustomEmoji: this.getCustomEmoji,
navToRoomInfo: this.navToRoomInfo navToRoomInfo: this.navToRoomInfo,
onPress: () => this.jumpToMessage({ item })
}); });
return ({ return ({
@ -315,7 +345,8 @@ const mapStateToProps = state => ({
baseUrl: state.server.server, baseUrl: state.server.server,
user: getUserSelector(state), user: getUserSelector(state),
customEmojis: state.customEmojis, customEmojis: state.customEmojis,
useRealName: state.settings.UI_Use_Real_Name useRealName: state.settings.UI_Use_Real_Name,
isMasterDetail: state.app.isMasterDetail
}); });
export default connect(mapStateToProps)(withTheme(withActionSheet(MessagesView))); export default connect(mapStateToProps)(withTheme(withActionSheet(MessagesView)));

View File

@ -25,6 +25,7 @@ import Navigation from '../lib/Navigation';
import { createChannelRequest } from '../actions/createChannel'; import { createChannelRequest } from '../actions/createChannel';
import { goRoom } from '../utils/goRoom'; import { goRoom } from '../utils/goRoom';
import SafeAreaView from '../containers/SafeAreaView'; import SafeAreaView from '../containers/SafeAreaView';
import { compareServerVersion, methods } from '../lib/utils';
const QUERY_SIZE = 50; const QUERY_SIZE = 50;
@ -60,10 +61,11 @@ class NewMessageView extends React.Component {
id: PropTypes.string, id: PropTypes.string,
token: PropTypes.string token: PropTypes.string
}), }),
createChannel: PropTypes.func, create: PropTypes.func,
maxUsers: PropTypes.number, maxUsers: PropTypes.number,
theme: PropTypes.string, theme: PropTypes.string,
isMasterDetail: PropTypes.bool isMasterDetail: PropTypes.bool,
serverVersion: PropTypes.string
}; };
constructor(props) { constructor(props) {
@ -116,11 +118,17 @@ class NewMessageView extends React.Component {
navigation.navigate('SelectedUsersViewCreateChannel', { nextAction: () => navigation.navigate('CreateChannelView') }); navigation.navigate('SelectedUsersViewCreateChannel', { nextAction: () => navigation.navigate('CreateChannelView') });
} }
createTeam = () => {
logEvent(events.NEW_MSG_CREATE_TEAM);
const { navigation } = this.props;
navigation.navigate('SelectedUsersViewCreateChannel', { nextAction: () => navigation.navigate('CreateChannelView', { isTeam: true }) });
}
createGroupChat = () => { createGroupChat = () => {
logEvent(events.NEW_MSG_CREATE_GROUP_CHAT); logEvent(events.NEW_MSG_CREATE_GROUP_CHAT);
const { createChannel, maxUsers, navigation } = this.props; const { create, maxUsers, navigation } = this.props;
navigation.navigate('SelectedUsersViewCreateChannel', { navigation.navigate('SelectedUsersViewCreateChannel', {
nextAction: () => createChannel({ group: true }), nextAction: () => create({ group: true }),
buttonText: I18n.t('Create'), buttonText: I18n.t('Create'),
maxUsers maxUsers
}); });
@ -160,7 +168,7 @@ class NewMessageView extends React.Component {
} }
renderHeader = () => { renderHeader = () => {
const { maxUsers, theme } = this.props; const { maxUsers, theme, serverVersion } = this.props;
return ( return (
<View style={{ backgroundColor: themes[theme].auxiliaryBackground }}> <View style={{ backgroundColor: themes[theme].auxiliaryBackground }}>
<SearchBox onChangeText={text => this.onSearchChangeText(text)} testID='new-message-view-search' /> <SearchBox onChangeText={text => this.onSearchChangeText(text)} testID='new-message-view-search' />
@ -172,6 +180,13 @@ class NewMessageView extends React.Component {
testID: 'new-message-view-create-channel', testID: 'new-message-view-create-channel',
first: true first: true
})} })}
{compareServerVersion(serverVersion, '3.13.0', methods.greaterThanOrEqualTo)
? (this.renderButton({
onPress: this.createTeam,
title: I18n.t('Create_Team'),
icon: 'teams',
testID: 'new-message-view-create-team'
})) : null}
{maxUsers > 2 ? this.renderButton({ {maxUsers > 2 ? this.renderButton({
onPress: this.createGroupChat, onPress: this.createGroupChat,
title: I18n.t('Create_Direct_Messages'), title: I18n.t('Create_Direct_Messages'),
@ -246,6 +261,7 @@ class NewMessageView extends React.Component {
} }
const mapStateToProps = state => ({ const mapStateToProps = state => ({
serverVersion: state.server.version,
isMasterDetail: state.app.isMasterDetail, isMasterDetail: state.app.isMasterDetail,
baseUrl: state.server.server, baseUrl: state.server.server,
maxUsers: state.settings.DirectMesssage_maxUsers || 1, maxUsers: state.settings.DirectMesssage_maxUsers || 1,
@ -253,7 +269,7 @@ const mapStateToProps = state => ({
}); });
const mapDispatchToProps = dispatch => ({ const mapDispatchToProps = dispatch => ({
createChannel: params => dispatch(createChannelRequest(params)) create: params => dispatch(createChannelRequest(params))
}); });
export default connect(mapStateToProps, mapDispatchToProps)(withTheme(NewMessageView)); export default connect(mapStateToProps, mapDispatchToProps)(withTheme(NewMessageView));

View File

@ -30,7 +30,7 @@ class ReadReceiptView extends React.Component {
static propTypes = { static propTypes = {
route: PropTypes.object, route: PropTypes.object,
Message_TimeFormat: PropTypes.string, Message_TimeAndDateFormat: PropTypes.string,
theme: PropTypes.string theme: PropTypes.string
} }
@ -94,8 +94,8 @@ class ReadReceiptView extends React.Component {
} }
renderItem = ({ item }) => { renderItem = ({ item }) => {
const { Message_TimeFormat, theme } = this.props; const { theme, Message_TimeAndDateFormat } = this.props;
const time = moment(item.ts).format(Message_TimeFormat); const time = moment(item.ts).format(Message_TimeAndDateFormat);
if (!item?.user?.username) { if (!item?.user?.username) {
return null; return null;
} }
@ -156,7 +156,7 @@ class ReadReceiptView extends React.Component {
} }
const mapStateToProps = state => ({ const mapStateToProps = state => ({
Message_TimeFormat: state.settings.Message_TimeFormat Message_TimeAndDateFormat: state.settings.Message_TimeAndDateFormat
}); });
export default connect(mapStateToProps)(withTheme(ReadReceiptView)); export default connect(mapStateToProps)(withTheme(ReadReceiptView));

View File

@ -1,15 +1,18 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { import {
View, Text, Alert, Share, Switch View, Text, Share, Switch
} from 'react-native'; } from 'react-native';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import isEmpty from 'lodash/isEmpty'; import isEmpty from 'lodash/isEmpty';
import { compareServerVersion, methods } from '../../lib/utils'; import { Q } from '@nozbe/watermelondb';
import { compareServerVersion, methods } from '../../lib/utils';
import Touch from '../../utils/touch'; import Touch from '../../utils/touch';
import { setLoading as setLoadingAction } from '../../actions/selectedUsers'; import { setLoading as setLoadingAction } from '../../actions/selectedUsers';
import { leaveRoom as leaveRoomAction, closeRoom as closeRoomAction } from '../../actions/room'; import {
leaveRoom as leaveRoomAction, closeRoom as closeRoomAction
} from '../../actions/room';
import styles from './styles'; import styles from './styles';
import sharedStyles from '../Styles'; import sharedStyles from '../Styles';
import Avatar from '../../containers/Avatar'; import Avatar from '../../containers/Avatar';
@ -60,7 +63,9 @@ class RoomActionsView extends React.Component {
editRoomPermission: PropTypes.array, editRoomPermission: PropTypes.array,
toggleRoomE2EEncryptionPermission: PropTypes.array, toggleRoomE2EEncryptionPermission: PropTypes.array,
viewBroadcastMemberListPermission: PropTypes.array, viewBroadcastMemberListPermission: PropTypes.array,
transferLivechatGuestPermission: PropTypes.array transferLivechatGuestPermission: PropTypes.array,
createTeamPermission: PropTypes.array,
addTeamChannelPermission: PropTypes.array
} }
constructor(props) { constructor(props) {
@ -82,7 +87,9 @@ class RoomActionsView extends React.Component {
canForwardGuest: false, canForwardGuest: false,
canReturnQueue: false, canReturnQueue: false,
canEdit: false, canEdit: false,
canToggleEncryption: false canToggleEncryption: false,
canCreateTeam: false,
canAddChannelToTeam: false
}; };
if (room && room.observe && room.rid) { if (room && room.observe && room.rid) {
this.roomObservable = room.observe(); this.roomObservable = room.observe();
@ -131,9 +138,11 @@ class RoomActionsView extends React.Component {
const canEdit = await this.canEdit(); const canEdit = await this.canEdit();
const canToggleEncryption = await this.canToggleEncryption(); const canToggleEncryption = await this.canToggleEncryption();
const canViewMembers = await this.canViewMembers(); const canViewMembers = await this.canViewMembers();
const canCreateTeam = await this.canCreateTeam();
const canAddChannelToTeam = await this.canAddChannelToTeam();
this.setState({ this.setState({
canAutoTranslate, canAddUser, canInviteUser, canEdit, canToggleEncryption, canViewMembers canAutoTranslate, canAddUser, canInviteUser, canEdit, canToggleEncryption, canViewMembers, canCreateTeam, canAddChannelToTeam
}); });
// livechat permissions // livechat permissions
@ -209,6 +218,26 @@ class RoomActionsView extends React.Component {
return canEdit; return canEdit;
} }
canCreateTeam = async() => {
const { room } = this.state;
const { createTeamPermission } = this.props;
const { rid } = room;
const permissions = await RocketChat.hasPermission([createTeamPermission], rid);
const canCreateTeam = permissions[0];
return canCreateTeam;
}
canAddChannelToTeam = async() => {
const { room } = this.state;
const { addTeamChannelPermission } = this.props;
const { rid } = room;
const permissions = await RocketChat.hasPermission([addTeamChannelPermission], rid);
const canAddChannelToTeam = permissions[0];
return canAddChannelToTeam;
}
canToggleEncryption = async() => { canToggleEncryption = async() => {
const { room } = this.state; const { room } = this.state;
const { toggleRoomE2EEncryptionPermission } = this.props; const { toggleRoomE2EEncryptionPermission } = this.props;
@ -395,21 +424,162 @@ class RoomActionsView extends React.Component {
const { room } = this.state; const { room } = this.state;
const { leaveRoom } = this.props; const { leaveRoom } = this.props;
Alert.alert( showConfirmationAlert({
I18n.t('Are_you_sure_question_mark'), message: I18n.t('Are_you_sure_you_want_to_leave_the_room', { room: RocketChat.getRoomTitle(room) }),
I18n.t('Are_you_sure_you_want_to_leave_the_room', { room: RocketChat.getRoomTitle(room) }), confirmationText: I18n.t('Yes_action_it', { action: I18n.t('leave') }),
[ onPress: () => leaveRoom('channel', room)
{ });
text: I18n.t('Cancel'), }
style: 'cancel'
}, leaveTeam = async() => {
{ const { room } = this.state;
text: I18n.t('Yes_action_it', { action: I18n.t('leave') }), const { navigation, leaveRoom } = this.props;
style: 'destructive',
onPress: () => leaveRoom(room.rid, room.t) try {
} const result = await RocketChat.teamListRoomsOfUser({ teamId: room.teamId, userId: room.u._id });
]
); if (result.rooms?.length) {
const teamChannels = result.rooms.map(r => ({
rid: r._id,
name: r.name,
teamId: r.teamId,
alert: r.isLastOwner
}));
navigation.navigate('SelectListView', {
title: 'Leave_Team',
data: teamChannels,
infoText: 'Select_Team_Channels',
nextAction: data => leaveRoom('team', room, data),
showAlert: () => showErrorAlert(I18n.t('Last_owner_team_room'), I18n.t('Cannot_leave'))
});
} else {
showConfirmationAlert({
message: I18n.t('You_are_leaving_the_team', { team: RocketChat.getRoomTitle(room) }),
confirmationText: I18n.t('Yes_action_it', { action: I18n.t('leave') }),
onPress: () => leaveRoom('team', room)
});
}
} catch (e) {
showConfirmationAlert({
message: I18n.t('You_are_leaving_the_team', { team: RocketChat.getRoomTitle(room) }),
confirmationText: I18n.t('Yes_action_it', { action: I18n.t('leave') }),
onPress: () => leaveRoom('team', room)
});
}
}
handleConvertToTeam = async() => {
logEvent(events.RA_CONVERT_TO_TEAM);
try {
const { room } = this.state;
const { navigation } = this.props;
const result = await RocketChat.convertChannelToTeam({ rid: room.rid, name: room.name, type: room.t });
if (result.success) {
navigation.navigate('RoomView');
}
} catch (e) {
logEvent(events.RA_CONVERT_TO_TEAM_F);
log(e);
}
}
convertToTeam = () => {
showConfirmationAlert({
title: I18n.t('Confirmation'),
message: I18n.t('Convert_to_Team_Warning'),
confirmationText: I18n.t('Convert'),
onPress: () => this.handleConvertToTeam()
});
}
handleMoveToTeam = async(selected) => {
logEvent(events.RA_MOVE_TO_TEAM);
try {
const { room } = this.state;
const { navigation } = this.props;
const result = await RocketChat.addRoomsToTeam({ teamId: selected?.[0], rooms: [room.rid] });
if (result.success) {
navigation.navigate('RoomView');
}
} catch (e) {
logEvent(events.RA_MOVE_TO_TEAM_F);
log(e);
showErrorAlert(I18n.t('There_was_an_error_while_action', { action: I18n.t('moving_channel_to_team') }));
}
}
moveToTeam = async() => {
try {
const { navigation } = this.props;
const db = database.active;
const subCollection = db.get('subscriptions');
const teamRooms = await subCollection.query(
Q.where('team_main', true)
);
if (teamRooms.length) {
const data = teamRooms.map(team => ({
rid: team.teamId,
t: team.t,
name: team.name
}));
navigation.navigate('SelectListView', {
title: 'Move_to_Team',
infoText: 'Move_Channel_Paragraph',
nextAction: () => {
navigation.push('SelectListView', {
title: 'Select_Team',
data,
isRadio: true,
isSearch: true,
onSearch: onChangeText => this.searchTeam(onChangeText),
nextAction: selected => showConfirmationAlert({
title: I18n.t('Confirmation'),
message: I18n.t('Move_to_Team_Warning'),
confirmationText: I18n.t('Yes_action_it', { action: I18n.t('move') }),
onPress: () => this.handleMoveToTeam(selected)
})
});
}
});
}
} catch (e) {
log(e);
}
}
searchTeam = async(onChangeText) => {
logEvent(events.RA_SEARCH_TEAM);
try {
const { addTeamChannelPermission, createTeamPermission } = this.props;
const QUERY_SIZE = 50;
const db = database.active;
const teams = await db.collections
.get('subscriptions')
.query(
Q.where('team_main', true),
Q.where('name', Q.like(`%${ onChangeText }%`)),
Q.experimentalTake(QUERY_SIZE),
Q.experimentalSortBy('room_updated_at', Q.desc)
);
const asyncFilter = async(teamArray) => {
const results = await Promise.all(teamArray.map(async(team) => {
const permissions = await RocketChat.hasPermission([addTeamChannelPermission, createTeamPermission], team.rid);
if (!permissions[0]) {
return false;
}
return true;
}));
return teamArray.filter((_v, index) => results[index]);
};
const teamsFiltered = await asyncFilter(teams);
return teamsFiltered;
} catch (e) {
log(e);
}
} }
renderRoomInfo = () => { renderRoomInfo = () => {
@ -486,7 +656,7 @@ class RoomActionsView extends React.Component {
renderJitsi = () => { renderJitsi = () => {
const { room } = this.state; const { room } = this.state;
const { jitsiEnabled } = this.props; const { jitsiEnabled } = this.props;
if (!jitsiEnabled) { if (!jitsiEnabled || room.teamMain) {
return null; return null;
} }
return ( return (
@ -544,7 +714,7 @@ class RoomActionsView extends React.Component {
return null; return null;
} }
if (t === 'd') { if (t === 'd' && !RocketChat.isGroupChat(room)) {
return ( return (
<List.Section> <List.Section>
<List.Separator /> <List.Separator />
@ -568,9 +738,9 @@ class RoomActionsView extends React.Component {
<List.Section> <List.Section>
<List.Separator /> <List.Separator />
<List.Item <List.Item
title='Leave_channel' title='Leave'
onPress={() => this.onPressTouchable({ onPress={() => this.onPressTouchable({
event: this.leaveChannel event: room.teamMain ? this.leaveTeam : this.leaveChannel
})} })}
testID='room-actions-leave-channel' testID='room-actions-leave-channel'
left={() => <List.Icon name='logout' color={themes[theme].dangerColor} />} left={() => <List.Icon name='logout' color={themes[theme].dangerColor} />}
@ -581,6 +751,52 @@ class RoomActionsView extends React.Component {
</List.Section> </List.Section>
); );
} }
return null;
}
teamChannelActions = (t, room) => {
const { canEdit, canCreateTeam, canAddChannelToTeam } = this.state;
const canConvertToTeam = canEdit && canCreateTeam && !room.teamMain;
const canMoveToTeam = canEdit && canAddChannelToTeam && !room.teamId;
return (
<>
{['c', 'p'].includes(t) && canConvertToTeam
? (
<>
<List.Item
title='Convert_to_Team'
onPress={() => this.onPressTouchable({
event: this.convertToTeam
})}
testID='room-actions-convert-to-team'
left={() => <List.Icon name='teams' />}
showActionIndicator
/>
<List.Separator />
</>
)
: null}
{['c', 'p'].includes(t) && canMoveToTeam
? (
<>
<List.Item
title='Move_to_Team'
onPress={() => this.onPressTouchable({
event: this.moveToTeam
})}
testID='room-actions-move-to-team'
left={() => <List.Icon name='channel-move-to-team' />}
showActionIndicator
/>
<List.Separator />
</>
)
: null}
</>
);
} }
render() { render() {
@ -588,7 +804,7 @@ class RoomActionsView extends React.Component {
room, membersCount, canViewMembers, canAddUser, canInviteUser, joined, canAutoTranslate, canForwardGuest, canReturnQueue room, membersCount, canViewMembers, canAddUser, canInviteUser, joined, canAutoTranslate, canForwardGuest, canReturnQueue
} = this.state; } = this.state;
const { const {
rid, t, encrypted rid, t
} = room; } = room;
const isGroupChat = RocketChat.isGroupChat(room); const isGroupChat = RocketChat.isGroupChat(room);
@ -713,24 +929,6 @@ class RoomActionsView extends React.Component {
) )
: null} : null}
{['c', 'p', 'd'].includes(t)
? (
<>
<List.Item
title='Search'
onPress={() => this.onPressTouchable({
route: 'SearchMessagesView',
params: { rid, encrypted }
})}
testID='room-actions-search'
left={() => <List.Icon name='search' />}
showActionIndicator
/>
<List.Separator />
</>
)
: null}
{['c', 'p', 'd'].includes(t) {['c', 'p', 'd'].includes(t)
? ( ? (
<> <>
@ -802,6 +1000,8 @@ class RoomActionsView extends React.Component {
) )
: null} : null}
{ this.teamChannelActions(t, room) }
{['l'].includes(t) && !this.isOmnichannelPreview {['l'].includes(t) && !this.isOmnichannelPreview
? ( ? (
<> <>
@ -880,6 +1080,7 @@ const mapStateToProps = state => ({
jitsiEnabled: state.settings.Jitsi_Enabled || false, jitsiEnabled: state.settings.Jitsi_Enabled || false,
encryptionEnabled: state.encryption.enabled, encryptionEnabled: state.encryption.enabled,
serverVersion: state.server.version, serverVersion: state.server.version,
isMasterDetail: state.app.isMasterDetail,
addUserToJoinedRoomPermission: state.permissions['add-user-to-joined-room'], addUserToJoinedRoomPermission: state.permissions['add-user-to-joined-room'],
addUserToAnyCRoomPermission: state.permissions['add-user-to-any-c-room'], addUserToAnyCRoomPermission: state.permissions['add-user-to-any-c-room'],
addUserToAnyPRoomPermission: state.permissions['add-user-to-any-p-room'], addUserToAnyPRoomPermission: state.permissions['add-user-to-any-p-room'],
@ -887,11 +1088,13 @@ const mapStateToProps = state => ({
editRoomPermission: state.permissions['edit-room'], editRoomPermission: state.permissions['edit-room'],
toggleRoomE2EEncryptionPermission: state.permissions['toggle-room-e2e-encryption'], toggleRoomE2EEncryptionPermission: state.permissions['toggle-room-e2e-encryption'],
viewBroadcastMemberListPermission: state.permissions['view-broadcast-member-list'], viewBroadcastMemberListPermission: state.permissions['view-broadcast-member-list'],
transferLivechatGuestPermission: state.permissions['transfer-livechat-guest'] transferLivechatGuestPermission: state.permissions['transfer-livechat-guest'],
createTeamPermission: state.permissions['create-team'],
addTeamChannelPermission: state.permissions['add-team-channel']
}); });
const mapDispatchToProps = dispatch => ({ const mapDispatchToProps = dispatch => ({
leaveRoom: (rid, t) => dispatch(leaveRoomAction(rid, t)), leaveRoom: (roomType, room, selected) => dispatch(leaveRoomAction(roomType, room, selected)),
closeRoom: rid => dispatch(closeRoomAction(rid)), closeRoom: rid => dispatch(closeRoomAction(rid)),
setLoadingInvite: loading => dispatch(setLoadingAction(loading)) setLoadingInvite: loading => dispatch(setLoadingAction(loading))
}); });

View File

@ -8,15 +8,16 @@ import { BLOCK_CONTEXT } from '@rocket.chat/ui-kit';
import ImagePicker from 'react-native-image-crop-picker'; import ImagePicker from 'react-native-image-crop-picker';
import { dequal } from 'dequal'; import { dequal } from 'dequal';
import isEmpty from 'lodash/isEmpty'; import isEmpty from 'lodash/isEmpty';
import { compareServerVersion, methods } from '../../lib/utils'; import { Q } from '@nozbe/watermelondb';
import { compareServerVersion, methods } from '../../lib/utils';
import database from '../../lib/database'; import database from '../../lib/database';
import { deleteRoom as deleteRoomAction } from '../../actions/room'; import { deleteRoom as deleteRoomAction } from '../../actions/room';
import KeyboardView from '../../presentation/KeyboardView'; import KeyboardView from '../../presentation/KeyboardView';
import sharedStyles from '../Styles'; import sharedStyles from '../Styles';
import styles from './styles'; import styles from './styles';
import scrollPersistTaps from '../../utils/scrollPersistTaps'; import scrollPersistTaps from '../../utils/scrollPersistTaps';
import { showErrorAlert } from '../../utils/info'; import { showConfirmationAlert, showErrorAlert } from '../../utils/info';
import { LISTENER } from '../../containers/Toast'; import { LISTENER } from '../../containers/Toast';
import EventEmitter from '../../utils/events'; import EventEmitter from '../../utils/events';
import RocketChat from '../../lib/rocketchat'; import RocketChat from '../../lib/rocketchat';
@ -41,6 +42,7 @@ const PERMISSION_ARCHIVE = 'archive-room';
const PERMISSION_UNARCHIVE = 'unarchive-room'; const PERMISSION_UNARCHIVE = 'unarchive-room';
const PERMISSION_DELETE_C = 'delete-c'; const PERMISSION_DELETE_C = 'delete-c';
const PERMISSION_DELETE_P = 'delete-p'; const PERMISSION_DELETE_P = 'delete-p';
const PERMISSION_DELETE_TEAM = 'delete-team';
class RoomInfoEditView extends React.Component { class RoomInfoEditView extends React.Component {
static navigationOptions = () => ({ static navigationOptions = () => ({
@ -48,6 +50,7 @@ class RoomInfoEditView extends React.Component {
}) })
static propTypes = { static propTypes = {
navigation: PropTypes.object,
route: PropTypes.object, route: PropTypes.object,
deleteRoom: PropTypes.func, deleteRoom: PropTypes.func,
serverVersion: PropTypes.string, serverVersion: PropTypes.string,
@ -58,7 +61,9 @@ class RoomInfoEditView extends React.Component {
archiveRoomPermission: PropTypes.array, archiveRoomPermission: PropTypes.array,
unarchiveRoomPermission: PropTypes.array, unarchiveRoomPermission: PropTypes.array,
deleteCPermission: PropTypes.array, deleteCPermission: PropTypes.array,
deletePPermission: PropTypes.array deletePPermission: PropTypes.array,
deleteTeamPermission: PropTypes.array,
isMasterDetail: PropTypes.bool
}; };
constructor(props) { constructor(props) {
@ -100,7 +105,8 @@ class RoomInfoEditView extends React.Component {
archiveRoomPermission, archiveRoomPermission,
unarchiveRoomPermission, unarchiveRoomPermission,
deleteCPermission, deleteCPermission,
deletePPermission deletePPermission,
deleteTeamPermission
} = this.props; } = this.props;
const rid = route.params?.rid; const rid = route.params?.rid;
if (!rid) { if (!rid) {
@ -122,7 +128,8 @@ class RoomInfoEditView extends React.Component {
archiveRoomPermission, archiveRoomPermission,
unarchiveRoomPermission, unarchiveRoomPermission,
deleteCPermission, deleteCPermission,
deletePPermission deletePPermission,
...(this.room.teamMain ? [deleteTeamPermission] : [])
], rid); ], rid);
this.setState({ this.setState({
@ -132,7 +139,8 @@ class RoomInfoEditView extends React.Component {
[PERMISSION_ARCHIVE]: result[2], [PERMISSION_ARCHIVE]: result[2],
[PERMISSION_UNARCHIVE]: result[3], [PERMISSION_UNARCHIVE]: result[3],
[PERMISSION_DELETE_C]: result[4], [PERMISSION_DELETE_C]: result[4],
[PERMISSION_DELETE_P]: result[5] [PERMISSION_DELETE_P]: result[5],
...(this.room.teamMain && { [PERMISSION_DELETE_TEAM]: result[6] })
} }
}); });
} catch (e) { } catch (e) {
@ -284,6 +292,74 @@ class RoomInfoEditView extends React.Component {
}, 100); }, 100);
} }
handleDeleteTeam = async(selected) => {
logEvent(events.RI_EDIT_DELETE_TEAM);
const { navigation, isMasterDetail } = this.props;
const { room } = this.state;
try {
const result = await RocketChat.deleteTeam({ teamId: room.teamId, ...(selected && { roomsToRemove: selected }) });
if (result.success) {
if (isMasterDetail) {
navigation.navigate('DrawerNavigator');
} else {
navigation.navigate('RoomsListView');
}
}
} catch (e) {
logEvent(events.RI_EDIT_DELETE_TEAM_F);
log(e);
showErrorAlert(
e.data.error
? I18n.t(e.data.error)
: I18n.t('There_was_an_error_while_action', { action: I18n.t('deleting_team') }),
I18n.t('Cannot_delete')
);
}
}
deleteTeam = async() => {
const { room } = this.state;
const { navigation } = this.props;
try {
const db = database.active;
const subCollection = db.get('subscriptions');
const teamChannels = await subCollection.query(
Q.where('team_id', room.teamId),
Q.where('team_main', Q.notEq(true))
);
if (teamChannels.length) {
navigation.navigate('SelectListView', {
title: 'Delete_Team',
data: teamChannels,
infoText: 'Select_channels_to_delete',
nextAction: (selected) => {
showConfirmationAlert({
message: I18n.t('You_are_deleting_the_team', { team: RocketChat.getRoomTitle(room) }),
confirmationText: I18n.t('Yes_action_it', { action: I18n.t('delete') }),
onPress: () => this.handleDeleteTeam(selected)
});
}
});
} else {
showConfirmationAlert({
message: I18n.t('You_are_deleting_the_team', { team: RocketChat.getRoomTitle(room) }),
confirmationText: I18n.t('Yes_action_it', { action: I18n.t('delete') }),
onPress: () => this.handleDeleteTeam()
});
}
} catch (e) {
log(e);
showErrorAlert(
e.data.error
? I18n.t(e.data.error)
: I18n.t('There_was_an_error_while_action', { action: I18n.t('deleting_team') }),
I18n.t('Cannot_delete')
);
}
}
delete = () => { delete = () => {
const { room } = this.state; const { room } = this.state;
const { deleteRoom } = this.props; const { deleteRoom } = this.props;
@ -339,9 +415,16 @@ class RoomInfoEditView extends React.Component {
hasDeletePermission = () => { hasDeletePermission = () => {
const { room, permissions } = this.state; const { room, permissions } = this.state;
return (
room.t === 'p' ? permissions[PERMISSION_DELETE_P] : permissions[PERMISSION_DELETE_C] if (room.teamMain) {
); return permissions[PERMISSION_DELETE_TEAM];
}
if (room.t === 'p') {
return permissions[PERMISSION_DELETE_P];
}
return permissions[PERMISSION_DELETE_C];
} }
hasArchivePermission = () => { hasArchivePermission = () => {
@ -513,9 +596,9 @@ class RoomInfoEditView extends React.Component {
<SwitchContainer <SwitchContainer
value={t} value={t}
leftLabelPrimary={I18n.t('Public')} leftLabelPrimary={I18n.t('Public')}
leftLabelSecondary={I18n.t('Everyone_can_access_this_channel')} leftLabelSecondary={room.teamMain ? I18n.t('Everyone_can_access_this_team') : I18n.t('Everyone_can_access_this_channel')}
rightLabelPrimary={I18n.t('Private')} rightLabelPrimary={I18n.t('Private')}
rightLabelSecondary={I18n.t('Just_invited_people_can_access_this_channel')} rightLabelSecondary={room.teamMain ? I18n.t('Just_invited_people_can_access_this_team') : I18n.t('Just_invited_people_can_access_this_channel')}
onValueChange={this.toggleRoomType} onValueChange={this.toggleRoomType}
theme={theme} theme={theme}
testID='room-info-edit-view-t' testID='room-info-edit-view-t'
@ -523,7 +606,7 @@ class RoomInfoEditView extends React.Component {
<SwitchContainer <SwitchContainer
value={ro} value={ro}
leftLabelPrimary={I18n.t('Collaborative')} leftLabelPrimary={I18n.t('Collaborative')}
leftLabelSecondary={I18n.t('All_users_in_the_channel_can_write_new_messages')} leftLabelSecondary={room.teamMain ? I18n.t('All_users_in_the_team_can_write_new_messages') : I18n.t('All_users_in_the_channel_can_write_new_messages')}
rightLabelPrimary={I18n.t('Read_Only')} rightLabelPrimary={I18n.t('Read_Only')}
rightLabelSecondary={I18n.t('Only_authorized_users_can_write_new_messages')} rightLabelSecondary={I18n.t('Only_authorized_users_can_write_new_messages')}
onValueChange={this.toggleReadOnly} onValueChange={this.toggleReadOnly}
@ -647,7 +730,7 @@ class RoomInfoEditView extends React.Component {
{ borderColor: dangerColor }, { borderColor: dangerColor },
!this.hasDeletePermission() && sharedStyles.opacity5 !this.hasDeletePermission() && sharedStyles.opacity5
]} ]}
onPress={this.delete} onPress={room.teamMain ? this.deleteTeam : this.delete}
disabled={!this.hasDeletePermission()} disabled={!this.hasDeletePermission()}
testID='room-info-edit-view-delete' testID='room-info-edit-view-delete'
> >
@ -678,7 +761,9 @@ const mapStateToProps = state => ({
archiveRoomPermission: state.permissions[PERMISSION_ARCHIVE], archiveRoomPermission: state.permissions[PERMISSION_ARCHIVE],
unarchiveRoomPermission: state.permissions[PERMISSION_UNARCHIVE], unarchiveRoomPermission: state.permissions[PERMISSION_UNARCHIVE],
deleteCPermission: state.permissions[PERMISSION_DELETE_C], deleteCPermission: state.permissions[PERMISSION_DELETE_C],
deletePPermission: state.permissions[PERMISSION_DELETE_P] deletePPermission: state.permissions[PERMISSION_DELETE_P],
deleteTeamPermission: state.permissions[PERMISSION_DELETE_TEAM],
isMasterDetail: state.app.isMasterDetail
}); });
const mapDispatchToProps = dispatch => ({ const mapDispatchToProps = dispatch => ({

View File

@ -214,7 +214,7 @@ class RoomInfoView extends React.Component {
} }
const permissions = await RocketChat.hasPermission([editRoomPermission], room.rid); const permissions = await RocketChat.hasPermission([editRoomPermission], room.rid);
if (permissions[0] && !room.prid) { if (permissions[0]) {
this.setState({ showEdit: true }, () => this.setHeader()); this.setState({ showEdit: true }, () => this.setHeader());
} }
} }

View File

@ -23,9 +23,10 @@ import { withTheme } from '../../theme';
import { themes } from '../../constants/colors'; import { themes } from '../../constants/colors';
import { getUserSelector } from '../../selectors/login'; import { getUserSelector } from '../../selectors/login';
import { withActionSheet } from '../../containers/ActionSheet'; import { withActionSheet } from '../../containers/ActionSheet';
import { showConfirmationAlert } from '../../utils/info'; import { showConfirmationAlert, showErrorAlert } from '../../utils/info';
import SafeAreaView from '../../containers/SafeAreaView'; import SafeAreaView from '../../containers/SafeAreaView';
import { goRoom } from '../../utils/goRoom'; import { goRoom } from '../../utils/goRoom';
import { CustomIcon } from '../../lib/Icons';
const PAGE_SIZE = 25; const PAGE_SIZE = 25;
@ -34,6 +35,9 @@ const PERMISSION_SET_LEADER = 'set-leader';
const PERMISSION_SET_OWNER = 'set-owner'; const PERMISSION_SET_OWNER = 'set-owner';
const PERMISSION_SET_MODERATOR = 'set-moderator'; const PERMISSION_SET_MODERATOR = 'set-moderator';
const PERMISSION_REMOVE_USER = 'remove-user'; const PERMISSION_REMOVE_USER = 'remove-user';
const PERMISSION_EDIT_TEAM_MEMBER = 'edit-team-member';
const PERMISION_VIEW_ALL_TEAMS = 'view-all-teams';
const PERMISSION_VIEW_ALL_TEAM_CHANNELS = 'view-all-team-channels';
class RoomMembersView extends React.Component { class RoomMembersView extends React.Component {
static propTypes = { static propTypes = {
@ -55,7 +59,10 @@ class RoomMembersView extends React.Component {
setLeaderPermission: PropTypes.array, setLeaderPermission: PropTypes.array,
setOwnerPermission: PropTypes.array, setOwnerPermission: PropTypes.array,
setModeratorPermission: PropTypes.array, setModeratorPermission: PropTypes.array,
removeUserPermission: PropTypes.array removeUserPermission: PropTypes.array,
editTeamMemberPermission: PropTypes.array,
viewAllTeamChannelsPermission: PropTypes.array,
viewAllTeamsPermission: PropTypes.array
} }
constructor(props) { constructor(props) {
@ -94,10 +101,11 @@ class RoomMembersView extends React.Component {
const { room } = this.state; const { room } = this.state;
const { const {
muteUserPermission, setLeaderPermission, setOwnerPermission, setModeratorPermission, removeUserPermission muteUserPermission, setLeaderPermission, setOwnerPermission, setModeratorPermission, removeUserPermission, editTeamMemberPermission, viewAllTeamChannelsPermission, viewAllTeamsPermission
} = this.props; } = this.props;
const result = await RocketChat.hasPermission([ const result = await RocketChat.hasPermission([
muteUserPermission, setLeaderPermission, setOwnerPermission, setModeratorPermission, removeUserPermission muteUserPermission, setLeaderPermission, setOwnerPermission, setModeratorPermission, removeUserPermission, ...(room.teamMain ? [editTeamMemberPermission, viewAllTeamChannelsPermission, viewAllTeamsPermission] : [])
], room.rid); ], room.rid);
this.permissions = { this.permissions = {
@ -105,7 +113,12 @@ class RoomMembersView extends React.Component {
[PERMISSION_SET_LEADER]: result[1], [PERMISSION_SET_LEADER]: result[1],
[PERMISSION_SET_OWNER]: result[2], [PERMISSION_SET_OWNER]: result[2],
[PERMISSION_SET_MODERATOR]: result[3], [PERMISSION_SET_MODERATOR]: result[3],
[PERMISSION_REMOVE_USER]: result[4] [PERMISSION_REMOVE_USER]: result[4],
...(room.teamMain ? {
[PERMISSION_EDIT_TEAM_MEMBER]: result[5],
[PERMISSION_VIEW_ALL_TEAM_CHANNELS]: result[6],
[PERMISION_VIEW_ALL_TEAMS]: result[7]
} : {})
}; };
const hasSinglePermission = Object.values(this.permissions).some(p => !!p); const hasSinglePermission = Object.values(this.permissions).some(p => !!p);
@ -137,6 +150,7 @@ class RoomMembersView extends React.Component {
onSearchChangeText = protectedFunction((text) => { onSearchChangeText = protectedFunction((text) => {
const { members } = this.state; const { members } = this.state;
let membersFiltered = []; let membersFiltered = [];
text = text.trim();
if (members && members.length > 0 && text) { if (members && members.length > 0 && text) {
membersFiltered = members.filter(m => m.username.toLowerCase().match(text.toLowerCase()) || m.name.toLowerCase().match(text.toLowerCase())); membersFiltered = members.filter(m => m.username.toLowerCase().match(text.toLowerCase()) || m.name.toLowerCase().match(text.toLowerCase()));
@ -163,9 +177,80 @@ class RoomMembersView extends React.Component {
} }
} }
handleRemoveFromTeam = async(selectedUser) => {
try {
const { navigation } = this.props;
const { room } = this.state;
const result = await RocketChat.teamListRoomsOfUser({ teamId: room.teamId, userId: selectedUser._id });
if (result.rooms?.length) {
const teamChannels = result.rooms.map(r => ({
rid: r._id,
name: r.name,
teamId: r.teamId,
alert: r.isLastOwner
}));
navigation.navigate('SelectListView', {
title: 'Remove_Member',
infoText: 'Remove_User_Team_Channels',
data: teamChannels,
nextAction: selected => this.removeFromTeam(selectedUser, selected),
showAlert: () => showErrorAlert(I18n.t('Last_owner_team_room'), I18n.t('Cannot_remove'))
});
} else {
showConfirmationAlert({
message: I18n.t('Removing_user_from_this_team', { user: selectedUser.username }),
confirmationText: I18n.t('Yes_action_it', { action: I18n.t('remove') }),
onPress: () => this.removeFromTeam(selectedUser)
});
}
} catch (e) {
showConfirmationAlert({
message: I18n.t('Removing_user_from_this_team', { user: selectedUser.username }),
confirmationText: I18n.t('Yes_action_it', { action: I18n.t('remove') }),
onPress: () => this.removeFromTeam(selectedUser)
});
}
}
removeFromTeam = async(selectedUser, selected) => {
try {
const { members, membersFiltered, room } = this.state;
const { navigation } = this.props;
const userId = selectedUser._id;
const result = await RocketChat.removeTeamMember({
teamId: room.teamId,
teamName: room.name,
userId,
...(selected && { rooms: selected })
});
if (result.success) {
const message = I18n.t('User_has_been_removed_from_s', { s: RocketChat.getRoomTitle(room) });
EventEmitter.emit(LISTENER, { message });
const newMembers = members.filter(member => member._id !== userId);
const newMembersFiltered = membersFiltered.filter(member => member._id !== userId);
this.setState({
members: newMembers,
membersFiltered: newMembersFiltered
});
navigation.navigate('RoomMembersView');
}
} catch (e) {
log(e);
showErrorAlert(
e.data.error
? I18n.t(e.data.error)
: I18n.t('There_was_an_error_while_action', { action: I18n.t('removing_team') }),
I18n.t('Cannot_remove')
);
}
}
onPressUser = (selectedUser) => { onPressUser = (selectedUser) => {
const { room } = this.state; const { room } = this.state;
const { showActionSheet, user } = this.props; const { showActionSheet, user, theme } = this.props;
const options = [{ const options = [{
icon: 'message', icon: 'message',
@ -173,39 +258,6 @@ class RoomMembersView extends React.Component {
onPress: () => this.navToDirectMessage(selectedUser) onPress: () => this.navToDirectMessage(selectedUser)
}]; }];
// Owner
if (this.permissions['set-owner']) {
const userRoleResult = this.roomRoles.find(r => r.u._id === selectedUser._id);
const isOwner = userRoleResult?.roles.includes('owner');
options.push({
icon: 'shield-check',
title: I18n.t(isOwner ? 'Remove_as_owner' : 'Set_as_owner'),
onPress: () => this.handleOwner(selectedUser, !isOwner)
});
}
// Leader
if (this.permissions['set-leader']) {
const userRoleResult = this.roomRoles.find(r => r.u._id === selectedUser._id);
const isLeader = userRoleResult?.roles.includes('leader');
options.push({
icon: 'shield-alt',
title: I18n.t(isLeader ? 'Remove_as_leader' : 'Set_as_leader'),
onPress: () => this.handleLeader(selectedUser, !isLeader)
});
}
// Moderator
if (this.permissions['set-moderator']) {
const userRoleResult = this.roomRoles.find(r => r.u._id === selectedUser._id);
const isModerator = userRoleResult?.roles.includes('moderator');
options.push({
icon: 'shield',
title: I18n.t(isModerator ? 'Remove_as_moderator' : 'Set_as_moderator'),
onPress: () => this.handleModerator(selectedUser, !isModerator)
});
}
// Ignore // Ignore
if (selectedUser._id !== user.id) { if (selectedUser._id !== user.id) {
const { ignored } = room; const { ignored } = room;
@ -213,7 +265,8 @@ class RoomMembersView extends React.Component {
options.push({ options.push({
icon: 'ignore', icon: 'ignore',
title: I18n.t(isIgnored ? 'Unignore' : 'Ignore'), title: I18n.t(isIgnored ? 'Unignore' : 'Ignore'),
onPress: () => this.handleIgnore(selectedUser, !isIgnored) onPress: () => this.handleIgnore(selectedUser, !isIgnored),
testID: 'action-sheet-ignore-user'
}); });
} }
@ -232,12 +285,63 @@ class RoomMembersView extends React.Component {
confirmationText: I18n.t(userIsMuted ? 'Unmute' : 'Mute'), confirmationText: I18n.t(userIsMuted ? 'Unmute' : 'Mute'),
onPress: () => this.handleMute(selectedUser) onPress: () => this.handleMute(selectedUser)
}); });
} },
testID: 'action-sheet-mute-user'
});
}
// Owner
if (this.permissions['set-owner']) {
const userRoleResult = this.roomRoles.find(r => r.u._id === selectedUser._id);
const isOwner = userRoleResult?.roles.includes('owner');
options.push({
icon: 'shield-check',
title: I18n.t('Owner'),
onPress: () => this.handleOwner(selectedUser, !isOwner),
right: () => <CustomIcon testID={isOwner ? 'action-sheet-set-owner-checked' : 'action-sheet-set-owner-unchecked'} name={isOwner ? 'checkbox-checked' : 'checkbox-unchecked'} size={20} color={isOwner ? themes[theme].tintActive : themes[theme].auxiliaryTintColor} />,
testID: 'action-sheet-set-owner'
});
}
// Leader
if (this.permissions['set-leader']) {
const userRoleResult = this.roomRoles.find(r => r.u._id === selectedUser._id);
const isLeader = userRoleResult?.roles.includes('leader');
options.push({
icon: 'shield-alt',
title: I18n.t('Leader'),
onPress: () => this.handleLeader(selectedUser, !isLeader),
right: () => <CustomIcon testID={isLeader ? 'action-sheet-set-leader-checked' : 'action-sheet-set-leader-unchecked'} name={isLeader ? 'checkbox-checked' : 'checkbox-unchecked'} size={20} color={isLeader ? themes[theme].tintActive : themes[theme].auxiliaryTintColor} />,
testID: 'action-sheet-set-leader'
});
}
// Moderator
if (this.permissions['set-moderator']) {
const userRoleResult = this.roomRoles.find(r => r.u._id === selectedUser._id);
const isModerator = userRoleResult?.roles.includes('moderator');
options.push({
icon: 'shield',
title: I18n.t('Moderator'),
onPress: () => this.handleModerator(selectedUser, !isModerator),
right: () => <CustomIcon testID={isModerator ? 'action-sheet-set-moderator-checked' : 'action-sheet-set-moderator-unchecked'} name={isModerator ? 'checkbox-checked' : 'checkbox-unchecked'} size={20} color={isModerator ? themes[theme].tintActive : themes[theme].auxiliaryTintColor} />,
testID: 'action-sheet-set-moderator'
});
}
// Remove from team
if (this.permissions['edit-team-member']) {
options.push({
icon: 'logout',
danger: true,
title: I18n.t('Remove_from_Team'),
onPress: () => this.handleRemoveFromTeam(selectedUser),
testID: 'action-sheet-remove-from-team'
}); });
} }
// Remove from room // Remove from room
if (this.permissions['remove-user']) { if (this.permissions['remove-user'] && !room.teamMain) {
options.push({ options.push({
icon: 'logout', icon: 'logout',
title: I18n.t('Remove_from_room'), title: I18n.t('Remove_from_room'),
@ -248,7 +352,8 @@ class RoomMembersView extends React.Component {
confirmationText: I18n.t('Yes_remove_user'), confirmationText: I18n.t('Yes_remove_user'),
onPress: () => this.handleRemoveUserFromRoom(selectedUser) onPress: () => this.handleRemoveUserFromRoom(selectedUser)
}); });
} },
testID: 'action-sheet-remove-from-room'
}); });
} }
@ -477,7 +582,10 @@ const mapStateToProps = state => ({
setLeaderPermission: state.permissions[PERMISSION_SET_LEADER], setLeaderPermission: state.permissions[PERMISSION_SET_LEADER],
setOwnerPermission: state.permissions[PERMISSION_SET_OWNER], setOwnerPermission: state.permissions[PERMISSION_SET_OWNER],
setModeratorPermission: state.permissions[PERMISSION_SET_MODERATOR], setModeratorPermission: state.permissions[PERMISSION_SET_MODERATOR],
removeUserPermission: state.permissions[PERMISSION_REMOVE_USER] removeUserPermission: state.permissions[PERMISSION_REMOVE_USER],
editTeamMemberPermission: state.permissions[PERMISSION_EDIT_TEAM_MEMBER],
viewAllTeamChannelsPermission: state.permissions[PERMISSION_VIEW_ALL_TEAM_CHANNELS],
viewAllTeamsPermission: state.permissions[PERMISION_VIEW_ALL_TEAMS]
}); });
export default connect(mapStateToProps)(withActionSheet(withTheme(RoomMembersView))); export default connect(mapStateToProps)(withActionSheet(withTheme(RoomMembersView)));

View File

@ -0,0 +1,42 @@
import React from 'react';
import { FlatList, StyleSheet } from 'react-native';
import Animated from 'react-native-reanimated';
import PropTypes from 'prop-types';
import { isIOS } from '../../../utils/deviceInfo';
import scrollPersistTaps from '../../../utils/scrollPersistTaps';
const AnimatedFlatList = Animated.createAnimatedComponent(FlatList);
const styles = StyleSheet.create({
list: {
flex: 1
},
contentContainer: {
paddingTop: 10
}
});
const List = ({ listRef, ...props }) => (
<AnimatedFlatList
testID='room-view-messages'
ref={listRef}
keyExtractor={item => item.id}
contentContainerStyle={styles.contentContainer}
style={styles.list}
inverted
removeClippedSubviews={isIOS}
initialNumToRender={7}
onEndReachedThreshold={0.5}
maxToRenderPerBatch={5}
windowSize={10}
{...props}
{...scrollPersistTaps}
/>
);
List.propTypes = {
listRef: PropTypes.object
};
export default List;

View File

@ -0,0 +1,75 @@
import React, { useCallback, useState } from 'react';
import { View, StyleSheet } from 'react-native';
import PropTypes from 'prop-types';
import Animated, {
call, cond, greaterOrEq, useCode
} from 'react-native-reanimated';
import { themes } from '../../../constants/colors';
import { CustomIcon } from '../../../lib/Icons';
import { useTheme } from '../../../theme';
import Touch from '../../../utils/touch';
import { hasNotch } from '../../../utils/deviceInfo';
const SCROLL_LIMIT = 200;
const SEND_TO_CHANNEL_HEIGHT = 40;
const styles = StyleSheet.create({
container: {
position: 'absolute',
right: 15
},
button: {
borderRadius: 25
},
content: {
width: 50,
height: 50,
borderRadius: 25,
borderWidth: 1,
alignItems: 'center',
justifyContent: 'center'
}
});
const NavBottomFAB = ({ y, onPress, isThread }) => {
const { theme } = useTheme();
const [show, setShow] = useState(false);
const handleOnPress = useCallback(() => onPress());
const toggle = v => setShow(v);
useCode(() => cond(greaterOrEq(y, SCROLL_LIMIT),
call([y], () => toggle(true)),
call([y], () => toggle(false))),
[y]);
if (!show) {
return null;
}
let bottom = hasNotch ? 100 : 60;
if (isThread) {
bottom += SEND_TO_CHANNEL_HEIGHT;
}
return (
<Animated.View style={[styles.container, { bottom }]}>
<Touch
onPress={handleOnPress}
theme={theme}
style={[styles.button, { backgroundColor: themes[theme].backgroundColor }]}
>
<View style={[styles.content, { borderColor: themes[theme].borderColor }]}>
<CustomIcon name='chevron-down' color={themes[theme].auxiliaryTintColor} size={36} />
</View>
</Touch>
</Animated.View>
);
};
NavBottomFAB.propTypes = {
y: Animated.Value,
onPress: PropTypes.func,
isThread: PropTypes.bool
};
export default NavBottomFAB;

View File

@ -1,30 +1,39 @@
import React from 'react'; import React from 'react';
import { FlatList, RefreshControl } from 'react-native'; import { RefreshControl } from 'react-native';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { Q } from '@nozbe/watermelondb'; import { Q } from '@nozbe/watermelondb';
import moment from 'moment'; import moment from 'moment';
import { dequal } from 'dequal'; import { dequal } from 'dequal';
import { Value, event } from 'react-native-reanimated';
import styles from './styles'; import database from '../../../lib/database';
import database from '../../lib/database'; import RocketChat from '../../../lib/rocketchat';
import scrollPersistTaps from '../../utils/scrollPersistTaps'; import log from '../../../utils/log';
import RocketChat from '../../lib/rocketchat'; import EmptyRoom from '../EmptyRoom';
import log from '../../utils/log'; import { animateNextTransition } from '../../../utils/layoutAnimation';
import EmptyRoom from './EmptyRoom'; import ActivityIndicator from '../../../containers/ActivityIndicator';
import { isIOS } from '../../utils/deviceInfo'; import { themes } from '../../../constants/colors';
import { animateNextTransition } from '../../utils/layoutAnimation'; import List from './List';
import ActivityIndicator from '../../containers/ActivityIndicator'; import NavBottomFAB from './NavBottomFAB';
import { themes } from '../../constants/colors'; import debounce from '../../../utils/debounce';
const QUERY_SIZE = 50; const QUERY_SIZE = 50;
class List extends React.Component { const onScroll = ({ y }) => event(
[
{
nativeEvent: {
contentOffset: { y }
}
}
],
{ useNativeDriver: true }
);
class ListContainer extends React.Component {
static propTypes = { static propTypes = {
onEndReached: PropTypes.func,
renderFooter: PropTypes.func,
renderRow: PropTypes.func, renderRow: PropTypes.func,
rid: PropTypes.string, rid: PropTypes.string,
t: PropTypes.string,
tmid: PropTypes.string, tmid: PropTypes.string,
theme: PropTypes.string, theme: PropTypes.string,
loading: PropTypes.bool, loading: PropTypes.bool,
@ -36,34 +45,28 @@ class List extends React.Component {
showMessageInMainThread: PropTypes.bool showMessageInMainThread: PropTypes.bool
}; };
// this.state.loading works for this.onEndReached and RoomView.init
static getDerivedStateFromProps(props, state) {
if (props.loading !== state.loading) {
return {
loading: props.loading
};
}
return null;
}
constructor(props) { constructor(props) {
super(props); super(props);
console.time(`${ this.constructor.name } init`); console.time(`${ this.constructor.name } init`);
console.time(`${ this.constructor.name } mount`); console.time(`${ this.constructor.name } mount`);
this.count = 0; this.count = 0;
this.needsFetch = false;
this.mounted = false; this.mounted = false;
this.animated = false; this.animated = false;
this.jumping = false;
this.state = { this.state = {
loading: true,
end: false,
messages: [], messages: [],
refreshing: false refreshing: false,
highlightedMessage: null
}; };
this.y = new Value(0);
this.onScroll = onScroll({ y: this.y });
this.query(); this.query();
this.unsubscribeFocus = props.navigation.addListener('focus', () => { this.unsubscribeFocus = props.navigation.addListener('focus', () => {
this.animated = true; this.animated = true;
}); });
this.viewabilityConfig = {
itemVisiblePercentThreshold: 10
};
console.timeEnd(`${ this.constructor.name } init`); console.timeEnd(`${ this.constructor.name } init`);
} }
@ -73,17 +76,17 @@ class List extends React.Component {
} }
shouldComponentUpdate(nextProps, nextState) { shouldComponentUpdate(nextProps, nextState) {
const { loading, end, refreshing } = this.state; const { refreshing, highlightedMessage } = this.state;
const { const {
hideSystemMessages, theme, tunread, ignored hideSystemMessages, theme, tunread, ignored, loading
} = this.props; } = this.props;
if (theme !== nextProps.theme) { if (theme !== nextProps.theme) {
return true; return true;
} }
if (loading !== nextState.loading) { if (loading !== nextProps.loading) {
return true; return true;
} }
if (end !== nextState.end) { if (highlightedMessage !== nextState.highlightedMessage) {
return true; return true;
} }
if (refreshing !== nextState.refreshing) { if (refreshing !== nextState.refreshing) {
@ -116,32 +119,14 @@ class List extends React.Component {
if (this.unsubscribeFocus) { if (this.unsubscribeFocus) {
this.unsubscribeFocus(); this.unsubscribeFocus();
} }
this.clearHighlightedMessageTimeout();
console.countReset(`${ this.constructor.name }.render calls`); console.countReset(`${ this.constructor.name }.render calls`);
} }
fetchData = async() => { clearHighlightedMessageTimeout = () => {
const { if (this.highlightedMessageTimeout) {
loading, end, messages, latest = messages[messages.length - 1]?.ts clearTimeout(this.highlightedMessageTimeout);
} = this.state; this.highlightedMessageTimeout = false;
if (loading || end) {
return;
}
this.setState({ loading: true });
const { rid, t, tmid } = this.props;
try {
let result;
if (tmid) {
// `offset` is `messages.length - 1` because we append thread start to `messages` obj
result = await RocketChat.loadThreadMessages({ tmid, rid, offset: messages.length - 1 });
} else {
result = await RocketChat.loadMessagesForRoom({ rid, t, latest });
}
this.setState({ end: result.length < QUERY_SIZE, loading: false, latest: result[result.length - 1]?.ts }, () => this.loadMoreMessages(result));
} catch (e) {
this.setState({ loading: false });
log(e);
} }
} }
@ -198,9 +183,6 @@ class List extends React.Component {
this.unsubscribeMessages(); this.unsubscribeMessages();
this.messagesSubscription = this.messagesObservable this.messagesSubscription = this.messagesObservable
.subscribe((messages) => { .subscribe((messages) => {
if (messages.length <= this.count) {
this.needsFetch = true;
}
if (tmid && this.thread) { if (tmid && this.thread) {
messages = [...messages, this.thread]; messages = [...messages, this.thread];
} }
@ -211,6 +193,7 @@ class List extends React.Component {
} else { } else {
this.state.messages = messages; this.state.messages = messages;
} }
// TODO: move it away from here
this.readThreads(); this.readThreads();
}); });
} }
@ -221,7 +204,7 @@ class List extends React.Component {
this.query(); this.query();
} }
readThreads = async() => { readThreads = debounce(async() => {
const { tmid } = this.props; const { tmid } = this.props;
if (tmid) { if (tmid) {
@ -231,39 +214,9 @@ class List extends React.Component {
// Do nothing // Do nothing
} }
} }
} }, 300)
onEndReached = async() => { onEndReached = () => this.query()
if (this.needsFetch) {
this.needsFetch = false;
await this.fetchData();
}
this.query();
}
loadMoreMessages = (result) => {
const { end } = this.state;
if (end) {
return;
}
// handle servers with version < 3.0.0
let { hideSystemMessages = [] } = this.props;
if (!Array.isArray(hideSystemMessages)) {
hideSystemMessages = [];
}
if (!hideSystemMessages.length) {
return;
}
const hasReadableMessages = result.filter(message => !message.t || (message.t && !hideSystemMessages.includes(message.t))).length > 0;
// if this batch doesn't contain any messages that will be displayed, we'll request a new batch
if (!hasReadableMessages) {
this.onEndReached();
}
}
onRefresh = () => this.setState({ refreshing: true }, async() => { onRefresh = () => this.setState({ refreshing: true }, async() => {
const { messages } = this.state; const { messages } = this.state;
@ -272,7 +225,7 @@ class List extends React.Component {
if (messages.length) { if (messages.length) {
try { try {
if (tmid) { if (tmid) {
await RocketChat.loadThreadMessages({ tmid, rid, offset: messages.length - 1 }); await RocketChat.loadThreadMessages({ tmid, rid });
} else { } else {
await RocketChat.loadMissedMessages({ rid, lastOpen: moment().subtract(7, 'days').toDate() }); await RocketChat.loadMissedMessages({ rid, lastOpen: moment().subtract(7, 'days').toDate() });
} }
@ -284,7 +237,6 @@ class List extends React.Component {
this.setState({ refreshing: false }); this.setState({ refreshing: false });
}) })
// eslint-disable-next-line react/sort-comp
update = () => { update = () => {
if (this.animated) { if (this.animated) {
animateNextTransition(); animateNextTransition();
@ -306,9 +258,53 @@ class List extends React.Component {
return null; return null;
} }
handleScrollToIndexFailed = (params) => {
const { listRef } = this.props;
listRef.current.getNode().scrollToIndex({ index: params.highestMeasuredFrameIndex, animated: false });
}
jumpToMessage = messageId => new Promise(async(resolve) => {
this.jumping = true;
const { messages } = this.state;
const { listRef } = this.props;
const index = messages.findIndex(item => item.id === messageId);
if (index > -1) {
listRef.current.getNode().scrollToIndex({ index, viewPosition: 0.5 });
await new Promise(res => setTimeout(res, 300));
if (!this.viewableItems.map(vi => vi.key).includes(messageId)) {
if (!this.jumping) {
return resolve();
}
await setTimeout(() => resolve(this.jumpToMessage(messageId)), 300);
return;
}
this.setState({ highlightedMessage: messageId });
this.clearHighlightedMessageTimeout();
this.highlightedMessageTimeout = setTimeout(() => {
this.setState({ highlightedMessage: null });
}, 10000);
await setTimeout(() => resolve(), 300);
} else {
listRef.current.getNode().scrollToIndex({ index: messages.length - 1, animated: false });
if (!this.jumping) {
return resolve();
}
await setTimeout(() => resolve(this.jumpToMessage(messageId)), 300);
}
});
// this.jumping is checked in between operations to make sure we're not stuck
cancelJumpToMessage = () => {
this.jumping = false;
}
jumpToBottom = () => {
const { listRef } = this.props;
listRef.current.getNode().scrollToOffset({ offset: -100 });
}
renderFooter = () => { renderFooter = () => {
const { loading } = this.state; const { rid, theme, loading } = this.props;
const { rid, theme } = this.props;
if (loading && rid) { if (loading && rid) {
return <ActivityIndicator theme={theme} />; return <ActivityIndicator theme={theme} />;
} }
@ -316,36 +312,34 @@ class List extends React.Component {
} }
renderItem = ({ item, index }) => { renderItem = ({ item, index }) => {
const { messages } = this.state; const { messages, highlightedMessage } = this.state;
const { renderRow } = this.props; const { renderRow } = this.props;
return renderRow(item, messages[index + 1]); return renderRow(item, messages[index + 1], highlightedMessage);
}
onViewableItemsChanged = ({ viewableItems }) => {
this.viewableItems = viewableItems;
} }
render() { render() {
console.count(`${ this.constructor.name }.render calls`); console.count(`${ this.constructor.name }.render calls`);
const { rid, listRef } = this.props; const { rid, tmid, listRef } = this.props;
const { messages, refreshing } = this.state; const { messages, refreshing } = this.state;
const { theme } = this.props; const { theme } = this.props;
return ( return (
<> <>
<EmptyRoom rid={rid} length={messages.length} mounted={this.mounted} theme={theme} /> <EmptyRoom rid={rid} length={messages.length} mounted={this.mounted} theme={theme} />
<FlatList <List
testID='room-view-messages' onScroll={this.onScroll}
ref={listRef} scrollEventThrottle={16}
keyExtractor={item => item.id} listRef={listRef}
data={messages} data={messages}
extraData={this.state}
renderItem={this.renderItem} renderItem={this.renderItem}
contentContainerStyle={styles.contentContainer}
style={styles.list}
inverted
removeClippedSubviews={isIOS}
initialNumToRender={7}
onEndReached={this.onEndReached} onEndReached={this.onEndReached}
onEndReachedThreshold={0.5}
maxToRenderPerBatch={5}
windowSize={10}
ListFooterComponent={this.renderFooter} ListFooterComponent={this.renderFooter}
onScrollToIndexFailed={this.handleScrollToIndexFailed}
onViewableItemsChanged={this.onViewableItemsChanged}
viewabilityConfig={this.viewabilityConfig}
refreshControl={( refreshControl={(
<RefreshControl <RefreshControl
refreshing={refreshing} refreshing={refreshing}
@ -353,11 +347,11 @@ class List extends React.Component {
tintColor={themes[theme].auxiliaryText} tintColor={themes[theme].auxiliaryText}
/> />
)} )}
{...scrollPersistTaps}
/> />
<NavBottomFAB y={this.y} onPress={this.jumpToBottom} isThread={!!tmid} />
</> </>
); );
} }
} }
export default List; export default ListContainer;

View File

@ -0,0 +1,62 @@
/* eslint-disable import/no-extraneous-dependencies, import/no-unresolved, import/extensions, react/prop-types, react/destructuring-assignment */
import React from 'react';
import { ScrollView } from 'react-native';
import { storiesOf } from '@storybook/react-native';
import LoadMore from './index';
import { longText } from '../../../../storybook/utils';
import { ThemeContext } from '../../../theme';
import {
Message, StoryProvider, MessageDecorator
} from '../../../../storybook/stories/Message';
import { themes } from '../../../constants/colors';
import { MESSAGE_TYPE_LOAD_MORE, MESSAGE_TYPE_LOAD_NEXT_CHUNK, MESSAGE_TYPE_LOAD_PREVIOUS_CHUNK } from '../../../constants/messageTypeLoad';
const stories = storiesOf('LoadMore', module);
// FIXME: for some reason, this promise never resolves on Storybook (it works on the app, so maybe the issue isn't on the component)
const load = () => new Promise(res => setTimeout(res, 1000));
stories.add('basic', () => (
<>
<LoadMore load={load} />
<LoadMore load={load} runOnRender />
<LoadMore load={load} type={MESSAGE_TYPE_LOAD_PREVIOUS_CHUNK} />
<LoadMore load={load} type={MESSAGE_TYPE_LOAD_NEXT_CHUNK} />
</>
));
const ThemeStory = ({ theme }) => (
<ThemeContext.Provider
value={{ theme }}
>
<ScrollView style={{ backgroundColor: themes[theme].backgroundColor }}>
<LoadMore load={load} type={MESSAGE_TYPE_LOAD_PREVIOUS_CHUNK} />
<Message msg='Hey!' theme={theme} />
<Message msg={longText} theme={theme} isHeader={false} />
<Message msg='Older message' theme={theme} isHeader={false} />
<LoadMore load={load} type={MESSAGE_TYPE_LOAD_NEXT_CHUNK} />
<LoadMore load={load} type={MESSAGE_TYPE_LOAD_MORE} />
<Message msg={longText} theme={theme} />
<Message msg='This is the third message' isHeader={false} theme={theme} />
<Message msg='This is the second message' isHeader={false} theme={theme} />
<Message msg='This is the first message' theme={theme} />
</ScrollView>
</ThemeContext.Provider>
);
stories
.addDecorator(StoryProvider)
.addDecorator(MessageDecorator)
.add('light theme', () => <ThemeStory theme='light' />);
stories
.addDecorator(StoryProvider)
.addDecorator(MessageDecorator)
.add('dark theme', () => <ThemeStory theme='dark' />);
stories
.addDecorator(StoryProvider)
.addDecorator(MessageDecorator)
.add('black theme', () => <ThemeStory theme='black' />);

View File

@ -0,0 +1,76 @@
import React, { useEffect, useCallback, useState } from 'react';
import { Text, StyleSheet, ActivityIndicator } from 'react-native';
import PropTypes from 'prop-types';
import { themes } from '../../../constants/colors';
import { MESSAGE_TYPE_LOAD_NEXT_CHUNK, MESSAGE_TYPE_LOAD_PREVIOUS_CHUNK } from '../../../constants/messageTypeLoad';
import { useTheme } from '../../../theme';
import Touch from '../../../utils/touch';
import sharedStyles from '../../Styles';
import I18n from '../../../i18n';
const styles = StyleSheet.create({
button: {
paddingVertical: 16,
alignItems: 'center',
justifyContent: 'center'
},
text: {
fontSize: 16,
...sharedStyles.textMedium
}
});
const LoadMore = ({ load, type, runOnRender }) => {
const { theme } = useTheme();
const [loading, setLoading] = useState(false);
const handleLoad = useCallback(async() => {
try {
if (loading) {
return;
}
setLoading(true);
await load();
} finally {
setLoading(false);
}
}, [loading]);
useEffect(() => {
if (runOnRender) {
handleLoad();
}
}, []);
let text = 'Load_More';
if (type === MESSAGE_TYPE_LOAD_NEXT_CHUNK) {
text = 'Load_Newer';
}
if (type === MESSAGE_TYPE_LOAD_PREVIOUS_CHUNK) {
text = 'Load_Older';
}
return (
<Touch
onPress={handleLoad}
style={styles.button}
theme={theme}
enabled={!loading}
>
{
loading
? <ActivityIndicator color={themes[theme].auxiliaryText} />
: <Text style={[styles.text, { color: themes[theme].titleText }]}>{I18n.t(text)}</Text>
}
</Touch>
);
};
LoadMore.propTypes = {
load: PropTypes.func,
type: PropTypes.string,
runOnRender: PropTypes.bool
};
export default LoadMore;

View File

@ -7,6 +7,7 @@ import * as HeaderButton from '../../containers/HeaderButton';
import database from '../../lib/database'; import database from '../../lib/database';
import { getUserSelector } from '../../selectors/login'; import { getUserSelector } from '../../selectors/login';
import { logEvent, events } from '../../utils/log'; import { logEvent, events } from '../../utils/log';
import { isTeamRoom } from '../../utils/room';
class RightButtonsContainer extends Component { class RightButtonsContainer extends Component {
static propTypes = { static propTypes = {
@ -15,10 +16,11 @@ class RightButtonsContainer extends Component {
rid: PropTypes.string, rid: PropTypes.string,
t: PropTypes.string, t: PropTypes.string,
tmid: PropTypes.string, tmid: PropTypes.string,
teamId: PropTypes.bool, teamId: PropTypes.string,
navigation: PropTypes.object, navigation: PropTypes.object,
isMasterDetail: PropTypes.bool, isMasterDetail: PropTypes.bool,
toggleFollowThread: PropTypes.func toggleFollowThread: PropTypes.func,
joined: PropTypes.bool
}; };
constructor(props) { constructor(props) {
@ -57,6 +59,10 @@ class RightButtonsContainer extends Component {
const { const {
isFollowingThread, tunread, tunreadUser, tunreadGroup isFollowingThread, tunread, tunreadUser, tunreadGroup
} = this.state; } = this.state;
const { teamId } = this.props;
if (nextProps.teamId !== teamId) {
return true;
}
if (nextState.isFollowingThread !== isFollowingThread) { if (nextState.isFollowingThread !== isFollowingThread) {
return true; return true;
} }
@ -140,12 +146,12 @@ class RightButtonsContainer extends Component {
goSearchView = () => { goSearchView = () => {
logEvent(events.ROOM_GO_SEARCH); logEvent(events.ROOM_GO_SEARCH);
const { const {
rid, navigation, isMasterDetail rid, t, navigation, isMasterDetail
} = this.props; } = this.props;
if (isMasterDetail) { if (isMasterDetail) {
navigation.navigate('ModalStackNavigator', { screen: 'SearchMessagesView', params: { rid, showCloseModal: true } }); navigation.navigate('ModalStackNavigator', { screen: 'SearchMessagesView', params: { rid, showCloseModal: true } });
} else { } else {
navigation.navigate('SearchMessagesView', { rid }); navigation.navigate('SearchMessagesView', { rid, t });
} }
} }
@ -163,7 +169,7 @@ class RightButtonsContainer extends Component {
isFollowingThread, tunread, tunreadUser, tunreadGroup isFollowingThread, tunread, tunreadUser, tunreadGroup
} = this.state; } = this.state;
const { const {
t, tmid, threadsEnabled, teamId t, tmid, threadsEnabled, teamId, joined
} = this.props; } = this.props;
if (t === 'l') { if (t === 'l') {
return null; return null;
@ -181,7 +187,7 @@ class RightButtonsContainer extends Component {
} }
return ( return (
<HeaderButton.Container> <HeaderButton.Container>
{teamId ? ( {isTeamRoom({ teamId, joined }) ? (
<HeaderButton.Item <HeaderButton.Item
iconName='channel-public' iconName='channel-public'
onPress={this.goTeamChannels} onPress={this.goTeamChannels}

View File

@ -2,8 +2,8 @@ import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { Text, View, InteractionManager } from 'react-native'; import { Text, View, InteractionManager } from 'react-native';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import parse from 'url-parse';
import { sanitizedRaw } from '@nozbe/watermelondb/RawRecord';
import moment from 'moment'; import moment from 'moment';
import * as Haptics from 'expo-haptics'; import * as Haptics from 'expo-haptics';
import { Q } from '@nozbe/watermelondb'; import { Q } from '@nozbe/watermelondb';
@ -17,7 +17,6 @@ import {
import List from './List'; import List from './List';
import database from '../../lib/database'; import database from '../../lib/database';
import RocketChat from '../../lib/rocketchat'; import RocketChat from '../../lib/rocketchat';
import { Encryption } from '../../lib/encryption';
import Message from '../../containers/message'; import Message from '../../containers/message';
import MessageActions from '../../containers/MessageActions'; import MessageActions from '../../containers/MessageActions';
import MessageErrorActions from '../../containers/MessageErrorActions'; import MessageErrorActions from '../../containers/MessageErrorActions';
@ -35,10 +34,13 @@ import RightButtons from './RightButtons';
import StatusBar from '../../containers/StatusBar'; import StatusBar from '../../containers/StatusBar';
import Separator from './Separator'; import Separator from './Separator';
import { themes } from '../../constants/colors'; import { themes } from '../../constants/colors';
import { MESSAGE_TYPE_ANY_LOAD, MESSAGE_TYPE_LOAD_MORE } from '../../constants/messageTypeLoad';
import debounce from '../../utils/debounce'; import debounce from '../../utils/debounce';
import ReactionsModal from '../../containers/ReactionsModal'; import ReactionsModal from '../../containers/ReactionsModal';
import { LISTENER } from '../../containers/Toast'; import { LISTENER } from '../../containers/Toast';
import { getBadgeColor, isBlocked, makeThreadName } from '../../utils/room'; import {
getBadgeColor, isBlocked, makeThreadName, isTeamRoom
} from '../../utils/room';
import { isReadOnly } from '../../utils/isReadOnly'; import { isReadOnly } from '../../utils/isReadOnly';
import { isIOS, isTablet } from '../../utils/deviceInfo'; import { isIOS, isTablet } from '../../utils/deviceInfo';
import { showErrorAlert } from '../../utils/info'; import { showErrorAlert } from '../../utils/info';
@ -62,6 +64,12 @@ import { getHeaderTitlePosition } from '../../containers/Header';
import { E2E_MESSAGE_TYPE, E2E_STATUS } from '../../lib/encryption/constants'; import { E2E_MESSAGE_TYPE, E2E_STATUS } from '../../lib/encryption/constants';
import { takeInquiry } from '../../ee/omnichannel/lib'; import { takeInquiry } from '../../ee/omnichannel/lib';
import Loading from '../../containers/Loading';
import LoadMore from './LoadMore';
import RoomServices from './services';
import { goRoom } from '../../utils/goRoom';
import getThreadName from '../../lib/methods/getThreadName';
import getRoomInfo from '../../lib/methods/getRoomInfo';
const stateAttrsUpdate = [ const stateAttrsUpdate = [
'joined', 'joined',
@ -74,9 +82,10 @@ const stateAttrsUpdate = [
'replying', 'replying',
'reacting', 'reacting',
'readOnly', 'readOnly',
'member' 'member',
'showingBlockingLoader'
]; ];
const roomAttrsUpdate = ['f', 'ro', 'blocked', 'blocker', 'archived', 'tunread', 'muted', 'ignored', 'jitsiTimeout', 'announcement', 'sysMes', 'topic', 'name', 'fname', 'roles', 'bannerClosed', 'visitor', 'joinCodeRequired']; const roomAttrsUpdate = ['f', 'ro', 'blocked', 'blocker', 'archived', 'tunread', 'muted', 'ignored', 'jitsiTimeout', 'announcement', 'sysMes', 'topic', 'name', 'fname', 'roles', 'bannerClosed', 'visitor', 'joinCodeRequired', 'teamMain', 'teamId'];
class RoomView extends React.Component { class RoomView extends React.Component {
static propTypes = { static propTypes = {
@ -115,11 +124,11 @@ class RoomView extends React.Component {
const selectedMessage = props.route.params?.message; const selectedMessage = props.route.params?.message;
const name = props.route.params?.name; const name = props.route.params?.name;
const fname = props.route.params?.fname; const fname = props.route.params?.fname;
const search = props.route.params?.search;
const prid = props.route.params?.prid; const prid = props.route.params?.prid;
const room = props.route.params?.room ?? { const room = props.route.params?.room ?? {
rid: this.rid, t: this.t, name, fname, prid rid: this.rid, t: this.t, name, fname, prid
}; };
this.jumpToMessageId = props.route.params?.jumpToMessageId;
const roomUserId = props.route.params?.roomUserId ?? RocketChat.getUidDirectMessage(room); const roomUserId = props.route.params?.roomUserId ?? RocketChat.getUidDirectMessage(room);
this.state = { this.state = {
joined: true, joined: true,
@ -131,6 +140,7 @@ class RoomView extends React.Component {
selectedMessage: selectedMessage || {}, selectedMessage: selectedMessage || {},
canAutoTranslate: false, canAutoTranslate: false,
loading: true, loading: true,
showingBlockingLoader: false,
editing: false, editing: false,
replying: !!selectedMessage, replying: !!selectedMessage,
replyWithMention: false, replyWithMention: false,
@ -149,13 +159,10 @@ class RoomView extends React.Component {
this.setReadOnly(); this.setReadOnly();
if (search) {
this.updateRoom();
}
this.messagebox = React.createRef(); this.messagebox = React.createRef();
this.list = React.createRef(); this.list = React.createRef();
this.joinCode = React.createRef(); this.joinCode = React.createRef();
this.flatList = React.createRef();
this.mounted = false; this.mounted = false;
// we don't need to subscribe to threads // we don't need to subscribe to threads
@ -179,6 +186,9 @@ class RoomView extends React.Component {
EventEmitter.addEventListener('connected', this.handleConnected); EventEmitter.addEventListener('connected', this.handleConnected);
} }
} }
if (this.jumpToMessageId) {
this.jumpToMessage(this.jumpToMessageId);
}
if (isIOS && this.rid) { if (isIOS && this.rid) {
this.updateUnreadCount(); this.updateUnreadCount();
} }
@ -193,7 +203,9 @@ class RoomView extends React.Component {
shouldComponentUpdate(nextProps, nextState) { shouldComponentUpdate(nextProps, nextState) {
const { state } = this; const { state } = this;
const { roomUpdate, member } = state; const { roomUpdate, member } = state;
const { appState, theme, insets } = this.props; const {
appState, theme, insets, route
} = this.props;
if (theme !== nextProps.theme) { if (theme !== nextProps.theme) {
return true; return true;
} }
@ -210,12 +222,19 @@ class RoomView extends React.Component {
if (!dequal(nextProps.insets, insets)) { if (!dequal(nextProps.insets, insets)) {
return true; return true;
} }
if (!dequal(nextProps.route?.params, route?.params)) {
return true;
}
return roomAttrsUpdate.some(key => !dequal(nextState.roomUpdate[key], roomUpdate[key])); return roomAttrsUpdate.some(key => !dequal(nextState.roomUpdate[key], roomUpdate[key]));
} }
componentDidUpdate(prevProps, prevState) { componentDidUpdate(prevProps, prevState) {
const { roomUpdate } = this.state; const { roomUpdate } = this.state;
const { appState, insets } = this.props; const { appState, insets, route } = this.props;
if (route?.params?.jumpToMessageId !== prevProps.route?.params?.jumpToMessageId) {
this.jumpToMessage(route?.params?.jumpToMessageId);
}
if (appState === 'foreground' && appState !== prevProps.appState && this.rid) { if (appState === 'foreground' && appState !== prevProps.appState && this.rid) {
// Fire List.query() just to keep observables working // Fire List.query() just to keep observables working
@ -235,7 +254,10 @@ class RoomView extends React.Component {
this.setHeader(); this.setHeader();
} }
} }
if (((roomUpdate.fname !== prevState.roomUpdate.fname) || (roomUpdate.name !== prevState.roomUpdate.name)) && !this.tmid) { if ((roomUpdate.teamMain !== prevState.roomUpdate.teamMain) || (roomUpdate.teamId !== prevState.roomUpdate.teamId)) {
this.setHeader();
}
if (((roomUpdate.fname !== prevState.roomUpdate.fname) || (roomUpdate.name !== prevState.roomUpdate.name) || (roomUpdate.teamMain !== prevState.roomUpdate.teamMain) || (roomUpdate.teamId !== prevState.roomUpdate.teamId)) && !this.tmid) {
this.setHeader(); this.setHeader();
} }
if (insets.left !== prevProps.insets.left || insets.right !== prevProps.insets.right) { if (insets.left !== prevProps.insets.left || insets.right !== prevProps.insets.right) {
@ -301,7 +323,7 @@ class RoomView extends React.Component {
setHeader = () => { setHeader = () => {
const { const {
room, unreadsCount, roomUserId room, unreadsCount, roomUserId, joined
} = this.state; } = this.state;
const { const {
navigation, isMasterDetail, theme, baseUrl, user, insets, route navigation, isMasterDetail, theme, baseUrl, user, insets, route
@ -331,7 +353,7 @@ class RoomView extends React.Component {
let numIconsRight = 2; let numIconsRight = 2;
if (tmid) { if (tmid) {
numIconsRight = 1; numIconsRight = 1;
} else if (teamId) { } else if (isTeamRoom({ teamId, joined })) {
numIconsRight = 3; numIconsRight = 3;
} }
const headerTitlePosition = getHeaderTitlePosition({ insets, numIconsRight }); const headerTitlePosition = getHeaderTitlePosition({ insets, numIconsRight });
@ -380,6 +402,8 @@ class RoomView extends React.Component {
rid={rid} rid={rid}
tmid={tmid} tmid={tmid}
teamId={teamId} teamId={teamId}
teamMain={teamMain}
joined={joined}
t={t} t={t}
navigation={navigation} navigation={navigation}
toggleFollowThread={this.toggleFollowThread} toggleFollowThread={this.toggleFollowThread}
@ -413,34 +437,15 @@ class RoomView extends React.Component {
this.setState({ readOnly }); this.setState({ readOnly });
} }
updateRoom = async() => {
const db = database.active;
try {
const subCollection = db.get('subscriptions');
const sub = await subCollection.find(this.rid);
const { room } = await RocketChat.getRoomInfo(this.rid);
await db.action(async() => {
await sub.update((s) => {
Object.assign(s, room);
});
});
} catch {
// do nothing
}
}
init = async() => { init = async() => {
try { try {
this.setState({ loading: true }); this.setState({ loading: true });
const { room, joined } = this.state; const { room, joined } = this.state;
if (this.tmid) { if (this.tmid) {
await this.getThreadMessages(); await RoomServices.getThreadMessages(this.tmid, this.rid);
} else { } else {
const newLastOpen = new Date(); const newLastOpen = new Date();
await this.getMessages(room); await RoomServices.getMessages(room);
// if room is joined // if room is joined
if (joined) { if (joined) {
@ -449,7 +454,7 @@ class RoomView extends React.Component {
} else { } else {
this.setLastOpen(null); this.setLastOpen(null);
} }
RocketChat.readMessages(room.rid, newLastOpen, true).catch(e => console.log(e)); RoomServices.readMessages(room.rid, newLastOpen, true).catch(e => console.log(e));
} }
} }
@ -656,26 +661,69 @@ class RoomView extends React.Component {
}); });
}; };
onThreadPress = debounce(async(item) => { onThreadPress = debounce(item => this.navToThread(item), 1000, true)
const { roomUserId } = this.state;
const { navigation } = this.props; shouldNavigateToRoom = (message) => {
if (item.tmid) { if (message.tmid && message.tmid === this.tmid) {
if (!item.tmsg) { return false;
await this.fetchThreadName(item.tmid, item.id);
}
let name = item.tmsg;
if (item.t === E2E_MESSAGE_TYPE && item.e2e !== E2E_STATUS.DONE) {
name = I18n.t('Encrypted_message');
}
navigation.push('RoomView', {
rid: item.subscription.id, tmid: item.tmid, name, t: 'thread', roomUserId
});
} else if (item.tlm) {
navigation.push('RoomView', {
rid: item.subscription.id, tmid: item.id, name: makeThreadName(item), t: 'thread', roomUserId
});
} }
}, 1000, true) if (!message.tmid && message.rid === this.rid) {
return false;
}
return true;
}
jumpToMessageByUrl = async(messageUrl) => {
if (!messageUrl) {
return;
}
try {
this.setState({ showingBlockingLoader: true });
const parsedUrl = parse(messageUrl, true);
const messageId = parsedUrl.query.msg;
await this.jumpToMessage(messageId);
this.setState({ showingBlockingLoader: false });
} catch (e) {
this.setState({ showingBlockingLoader: false });
log(e);
}
}
jumpToMessage = async(messageId) => {
try {
this.setState({ showingBlockingLoader: true });
const message = await RoomServices.getMessageInfo(messageId);
if (!message) {
return;
}
if (this.shouldNavigateToRoom(message)) {
if (message.rid !== this.rid) {
this.navToRoom(message);
} else {
this.navToThread(message);
}
} else {
/**
* if it's from server, we don't have it saved locally and so we fetch surroundings
* we test if it's not from threads because we're fetching from threads currently with `getThreadMessages`
*/
if (message.fromServer && !message.tmid) {
await RocketChat.loadSurroundingMessages({ messageId, rid: this.rid });
}
await Promise.race([
this.list.current.jumpToMessage(message.id),
new Promise(res => setTimeout(res, 5000))
]);
this.list.current.cancelJumpToMessage();
}
} catch (e) {
log(e);
} finally {
this.setState({ showingBlockingLoader: false });
}
}
replyBroadcast = (message) => { replyBroadcast = (message) => {
const { replyBroadcast } = this.props; const { replyBroadcast } = this.props;
@ -714,17 +762,6 @@ class RoomView extends React.Component {
}); });
}; };
getMessages = () => {
const { room } = this.state;
if (room.lastOpen) {
return RocketChat.loadMissedMessages(room);
} else {
return RocketChat.loadMessagesForRoom(room);
}
}
getThreadMessages = () => RocketChat.loadThreadMessages({ tmid: this.tmid, rid: this.rid })
getCustomEmoji = (name) => { getCustomEmoji = (name) => {
const { customEmojis } = this.props; const { customEmojis } = this.props;
const emoji = customEmojis[name]; const emoji = customEmojis[name];
@ -763,45 +800,7 @@ class RoomView extends React.Component {
} }
} }
// eslint-disable-next-line react/sort-comp getThreadName = (tmid, messageId) => getThreadName(this.rid, tmid, messageId)
fetchThreadName = async(tmid, messageId) => {
try {
const db = database.active;
const threadCollection = db.get('threads');
const messageCollection = db.get('messages');
const messageRecord = await messageCollection.find(messageId);
let threadRecord;
try {
threadRecord = await threadCollection.find(tmid);
} catch (error) {
console.log('Thread not found. We have to search for it.');
}
if (threadRecord) {
await db.action(async() => {
await messageRecord.update((m) => {
m.tmsg = threadRecord.msg || (threadRecord.attachments && threadRecord.attachments.length && threadRecord.attachments[0].title);
});
});
} else {
let { message: thread } = await RocketChat.getSingleMessage(tmid);
thread = await Encryption.decryptMessage(thread);
await db.action(async() => {
await db.batch(
threadCollection.prepareCreate((t) => {
t._raw = sanitizedRaw({ id: thread._id }, threadCollection.schema);
t.subscription.id = this.rid;
Object.assign(t, thread);
}),
messageRecord.prepareUpdate((m) => {
m.tmsg = thread.msg || (thread.attachments && thread.attachments.length && thread.attachments[0].title);
})
);
});
}
} catch (e) {
// log(e);
}
}
toggleFollowThread = async(isFollowingThread, tmid) => { toggleFollowThread = async(isFollowingThread, tmid) => {
try { try {
@ -832,6 +831,38 @@ class RoomView extends React.Component {
} }
} }
navToThread = async(item) => {
const { roomUserId } = this.state;
const { navigation } = this.props;
if (item.tmid) {
let name = item.tmsg;
if (!name) {
name = await this.getThreadName(item.tmid, item.id);
}
if (item.t === E2E_MESSAGE_TYPE && item.e2e !== E2E_STATUS.DONE) {
name = I18n.t('Encrypted_message');
}
return navigation.push('RoomView', {
rid: this.rid, tmid: item.tmid, name, t: 'thread', roomUserId, jumpToMessageId: item.id
});
}
if (item.tlm) {
return navigation.push('RoomView', {
rid: this.rid, tmid: item.id, name: makeThreadName(item), t: 'thread', roomUserId
});
}
}
navToRoom = async(message) => {
const { navigation, isMasterDetail } = this.props;
const roomInfo = await getRoomInfo(message.rid);
return goRoom({
item: roomInfo, isMasterDetail, navigationMethod: navigation.push, jumpToMessageId: message.id
});
}
callJitsi = () => { callJitsi = () => {
const { room } = this.state; const { room } = this.state;
const { jitsiTimeout } = room; const { jitsiTimeout } = room;
@ -896,7 +927,11 @@ class RoomView extends React.Component {
return room?.ignored?.includes?.(message?.u?._id) ?? false; return room?.ignored?.includes?.(message?.u?._id) ?? false;
} }
renderItem = (item, previousItem) => { onLoadMoreMessages = loaderItem => RoomServices.getMoreMessages({
rid: this.rid, tmid: this.tmid, t: this.t, loaderItem
})
renderItem = (item, previousItem, highlightedMessage) => {
const { room, lastOpen, canAutoTranslate } = this.state; const { room, lastOpen, canAutoTranslate } = this.state;
const { const {
user, Message_GroupingPeriod, Message_TimeFormat, useRealName, baseUrl, Message_Read_Receipt_Enabled, theme user, Message_GroupingPeriod, Message_TimeFormat, useRealName, baseUrl, Message_Read_Receipt_Enabled, theme
@ -916,48 +951,55 @@ class RoomView extends React.Component {
} }
} }
const message = ( let content = null;
<Message if (MESSAGE_TYPE_ANY_LOAD.includes(item.t)) {
item={item} content = <LoadMore load={() => this.onLoadMoreMessages(item)} type={item.t} runOnRender={item.t === MESSAGE_TYPE_LOAD_MORE && !previousItem} />;
user={user} } else {
rid={room.rid} content = (
archived={room.archived} <Message
broadcast={room.broadcast} item={item}
status={item.status} user={user}
isThreadRoom={!!this.tmid} rid={room.rid}
isIgnored={this.isIgnored(item)} archived={room.archived}
previousItem={previousItem} broadcast={room.broadcast}
fetchThreadName={this.fetchThreadName} status={item.status}
onReactionPress={this.onReactionPress} isThreadRoom={!!this.tmid}
onReactionLongPress={this.onReactionLongPress} isIgnored={this.isIgnored(item)}
onLongPress={this.onMessageLongPress} previousItem={previousItem}
onEncryptedPress={this.onEncryptedPress} fetchThreadName={this.getThreadName}
onDiscussionPress={this.onDiscussionPress} onReactionPress={this.onReactionPress}
onThreadPress={this.onThreadPress} onReactionLongPress={this.onReactionLongPress}
showAttachment={this.showAttachment} onLongPress={this.onMessageLongPress}
reactionInit={this.onReactionInit} onEncryptedPress={this.onEncryptedPress}
replyBroadcast={this.replyBroadcast} onDiscussionPress={this.onDiscussionPress}
errorActionsShow={this.errorActionsShow} onThreadPress={this.onThreadPress}
baseUrl={baseUrl} showAttachment={this.showAttachment}
Message_GroupingPeriod={Message_GroupingPeriod} reactionInit={this.onReactionInit}
timeFormat={Message_TimeFormat} replyBroadcast={this.replyBroadcast}
useRealName={useRealName} errorActionsShow={this.errorActionsShow}
isReadReceiptEnabled={Message_Read_Receipt_Enabled} baseUrl={baseUrl}
autoTranslateRoom={canAutoTranslate && room.autoTranslate} Message_GroupingPeriod={Message_GroupingPeriod}
autoTranslateLanguage={room.autoTranslateLanguage} timeFormat={Message_TimeFormat}
navToRoomInfo={this.navToRoomInfo} useRealName={useRealName}
getCustomEmoji={this.getCustomEmoji} isReadReceiptEnabled={Message_Read_Receipt_Enabled}
callJitsi={this.callJitsi} autoTranslateRoom={canAutoTranslate && room.autoTranslate}
blockAction={this.blockAction} autoTranslateLanguage={room.autoTranslateLanguage}
threadBadgeColor={this.getBadgeColor(item?.id)} navToRoomInfo={this.navToRoomInfo}
toggleFollowThread={this.toggleFollowThread} getCustomEmoji={this.getCustomEmoji}
/> callJitsi={this.callJitsi}
); blockAction={this.blockAction}
threadBadgeColor={this.getBadgeColor(item?.id)}
toggleFollowThread={this.toggleFollowThread}
jumpToMessage={this.jumpToMessageByUrl}
highlighted={highlightedMessage === item.id}
/>
);
}
if (showUnreadSeparator || dateSeparator) { if (showUnreadSeparator || dateSeparator) {
return ( return (
<> <>
{message} {content}
<Separator <Separator
ts={dateSeparator} ts={dateSeparator}
unread={showUnreadSeparator} unread={showUnreadSeparator}
@ -967,7 +1009,7 @@ class RoomView extends React.Component {
); );
} }
return message; return content;
} }
renderFooter = () => { renderFooter = () => {
@ -1053,12 +1095,10 @@ class RoomView extends React.Component {
); );
} }
setListRef = ref => this.flatList = ref;
render() { render() {
console.count(`${ this.constructor.name }.render calls`); console.count(`${ this.constructor.name }.render calls`);
const { const {
room, reactionsModalVisible, selectedMessage, loading, reacting room, reactionsModalVisible, selectedMessage, loading, reacting, showingBlockingLoader
} = this.state; } = this.state;
const { const {
user, baseUrl, theme, navigation, Hide_System_Messages, width, height user, baseUrl, theme, navigation, Hide_System_Messages, width, height
@ -1083,7 +1123,7 @@ class RoomView extends React.Component {
/> />
<List <List
ref={this.list} ref={this.list}
listRef={this.setListRef} listRef={this.flatList}
rid={rid} rid={rid}
t={t} t={t}
tmid={this.tmid} tmid={this.tmid}
@ -1123,6 +1163,7 @@ class RoomView extends React.Component {
t={t} t={t}
theme={theme} theme={theme}
/> />
<Loading visible={showingBlockingLoader} />
</SafeAreaView> </SafeAreaView>
); );
} }

View File

@ -0,0 +1,41 @@
import { getMessageById } from '../../../lib/database/services/Message';
import { getThreadMessageById } from '../../../lib/database/services/ThreadMessage';
import getSingleMessage from '../../../lib/methods/getSingleMessage';
const getMessageInfo = async(messageId) => {
let result;
result = await getMessageById(messageId);
if (result) {
return {
id: result.id,
rid: result.subscription.id,
tmid: result.tmid,
msg: result.msg
};
}
result = await getThreadMessageById(messageId);
if (result) {
return {
id: result.id,
rid: result.subscription.id,
tmid: result.rid,
msg: result.msg
};
}
result = await getSingleMessage(messageId);
if (result) {
return {
id: result._id,
rid: result.rid,
tmid: result.tmid,
msg: result.msg,
fromServer: true
};
}
return null;
};
export default getMessageInfo;

View File

@ -0,0 +1,10 @@
import RocketChat from '../../../lib/rocketchat';
const getMessages = (room) => {
if (room.lastOpen) {
return RocketChat.loadMissedMessages(room);
} else {
return RocketChat.loadMessagesForRoom(room);
}
};
export default getMessages;

View File

@ -0,0 +1,19 @@
import { MESSAGE_TYPE_LOAD_MORE, MESSAGE_TYPE_LOAD_NEXT_CHUNK, MESSAGE_TYPE_LOAD_PREVIOUS_CHUNK } from '../../../constants/messageTypeLoad';
import RocketChat from '../../../lib/rocketchat';
const getMoreMessages = ({
rid, t, tmid, loaderItem
}) => {
if ([MESSAGE_TYPE_LOAD_MORE, MESSAGE_TYPE_LOAD_PREVIOUS_CHUNK].includes(loaderItem.t)) {
return RocketChat.loadMessagesForRoom({
rid, t, latest: loaderItem.ts, loaderItem
});
}
if (loaderItem.t === MESSAGE_TYPE_LOAD_NEXT_CHUNK) {
return RocketChat.loadNextMessages({
rid, tmid, ts: loaderItem.ts, loaderItem
});
}
};
export default getMoreMessages;

View File

@ -0,0 +1,6 @@
import RocketChat from '../../../lib/rocketchat';
// unlike getMessages, sync isn't required for threads, because loadMissedMessages does it already
const getThreadMessages = (tmid, rid) => RocketChat.loadThreadMessages({ tmid, rid });
export default getThreadMessages;

View File

@ -0,0 +1,13 @@
import getMessages from './getMessages';
import getMoreMessages from './getMoreMessages';
import getThreadMessages from './getThreadMessages';
import readMessages from './readMessages';
import getMessageInfo from './getMessageInfo';
export default {
getMessages,
getMoreMessages,
getThreadMessages,
readMessages,
getMessageInfo
};

Some files were not shown because too many files have changed in this diff Show More