diff --git a/android/app/build.gradle b/android/app/build.gradle index 8179f860312bb78c70df7118c3862a9b78c30824..8c3ef1d6d1c6205ecbe313cf6dfa6c1fa968bc46 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -172,6 +172,7 @@ repositories { } dependencies { + compile project(':react-native-i18n') compile project(':react-native-fabric') compile project(':react-native-audio') compile project(":reactnativekeyboardinput") diff --git a/android/app/src/main/java/chat/rocketchat/reactnative/CustomTabsAndroid.java b/android/app/src/main/java/chat/rocket/reactnative/CustomTabsAndroid.java similarity index 100% rename from android/app/src/main/java/chat/rocketchat/reactnative/CustomTabsAndroid.java rename to android/app/src/main/java/chat/rocket/reactnative/CustomTabsAndroid.java diff --git a/android/app/src/main/java/chat/rocketchat/reactnative/CustomTabsHelper.java b/android/app/src/main/java/chat/rocket/reactnative/CustomTabsHelper.java similarity index 100% rename from android/app/src/main/java/chat/rocketchat/reactnative/CustomTabsHelper.java rename to android/app/src/main/java/chat/rocket/reactnative/CustomTabsHelper.java diff --git a/android/app/src/main/java/chat/rocketchat/reactnative/MainActivity.java b/android/app/src/main/java/chat/rocket/reactnative/MainActivity.java similarity index 100% rename from android/app/src/main/java/chat/rocketchat/reactnative/MainActivity.java rename to android/app/src/main/java/chat/rocket/reactnative/MainActivity.java diff --git a/android/app/src/main/java/chat/rocketchat/reactnative/MainApplication.java b/android/app/src/main/java/chat/rocket/reactnative/MainApplication.java similarity index 95% rename from android/app/src/main/java/chat/rocketchat/reactnative/MainApplication.java rename to android/app/src/main/java/chat/rocket/reactnative/MainApplication.java index 7883ed1b26d77b99e0eae7a74c7d177e9d7d4459..c6fbea41c5a9270c62abf95ea58a2fd1b5465d71 100644 --- a/android/app/src/main/java/chat/rocketchat/reactnative/MainApplication.java +++ b/android/app/src/main/java/chat/rocket/reactnative/MainApplication.java @@ -20,6 +20,7 @@ import com.wix.reactnativekeyboardinput.KeyboardInputPackage; import com.rnim.rn.audio.ReactNativeAudioPackage; import com.smixx.fabric.FabricPackage; import com.dylanvann.fastimage.FastImageViewPackage; +import com.AlexanderZaytsev.RNI18n.RNI18nPackage; import java.util.Arrays; import java.util.List; @@ -51,7 +52,8 @@ public class MainApplication extends Application implements ReactApplication { new KeyboardInputPackage(MainApplication.this), new RocketChatNativePackage(), new FabricPackage(), - new FastImageViewPackage() + new FastImageViewPackage(), + new RNI18nPackage() ); } }; diff --git a/android/app/src/main/java/chat/rocketchat/reactnative/RocketChatNativePackage.java b/android/app/src/main/java/chat/rocket/reactnative/RocketChatNativePackage.java similarity index 100% rename from android/app/src/main/java/chat/rocketchat/reactnative/RocketChatNativePackage.java rename to android/app/src/main/java/chat/rocket/reactnative/RocketChatNativePackage.java diff --git a/android/settings.gradle b/android/settings.gradle index 1259f2ba93b1d5f8044cc6f7cc36e5c33f1f005c..ffb155c5cf7fba872c8524ca883fe2bc59945b00 100644 --- a/android/settings.gradle +++ b/android/settings.gradle @@ -1,4 +1,6 @@ rootProject.name = 'RocketChatRN' +include ':react-native-i18n' +project(':react-native-i18n').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-i18n/android') include ':react-native-fast-image' project(':react-native-fast-image').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-fast-image/android') include ':react-native-fabric' diff --git a/app/containers/Banner.js b/app/containers/Banner.js deleted file mode 100644 index ae7b75d1f038c982938d3822d329f4a8b9692491..0000000000000000000000000000000000000000 --- a/app/containers/Banner.js +++ /dev/null @@ -1,69 +0,0 @@ -import { StyleSheet, View, Text } from 'react-native'; -import PropTypes from 'prop-types'; -import React from 'react'; - -import { connect } from 'react-redux'; - -const styles = StyleSheet.create({ - bannerContainer: { - backgroundColor: '#ddd' - }, - bannerText: { - textAlign: 'center', - margin: 5 - } -}); - -@connect(state => ({ - connecting: state.meteor.connecting, - authenticating: state.login.isFetching, - offline: !state.meteor.connected, - logged: !!state.login.token -})) - -export default class Banner extends React.PureComponent { - static propTypes = { - connecting: PropTypes.bool, - authenticating: PropTypes.bool, - offline: PropTypes.bool - } - render() { - const { - connecting, authenticating, offline, logged - } = this.props; - - if (offline) { - return ( - <View style={[styles.bannerContainer, { backgroundColor: 'red' }]}> - <Text style={[styles.bannerText, { color: '#a00' }]}>offline...</Text> - </View> - ); - } - - if (connecting) { - return ( - <View style={[styles.bannerContainer, { backgroundColor: '#0d0' }]}> - <Text style={[styles.bannerText, { color: '#fff' }]}>Connecting...</Text> - </View> - ); - } - - if (authenticating) { - return ( - <View style={[styles.bannerContainer, { backgroundColor: 'orange' }]}> - <Text style={[styles.bannerText, { color: '#a00' }]}>Authenticating...</Text> - </View> - ); - } - - if (logged) { - return this.props.children; - } - - return ( - <View style={[styles.bannerContainer, { backgroundColor: 'orange' }]}> - <Text style={[styles.bannerText, { color: '#a00' }]}>Not logged...</Text> - </View> - ); - } -} diff --git a/app/containers/MessageActions.js b/app/containers/MessageActions.js index d126600df3070f19550e2f7467ab8f78dbe52f1d..ee63c6ad960349aab0d027f45ed6a8a428c7ea63 100644 --- a/app/containers/MessageActions.js +++ b/app/containers/MessageActions.js @@ -18,6 +18,7 @@ import { } from '../actions/messages'; import { showToast } from '../utils/info'; import RocketChat from '../lib/rocketchat'; +import I18n from '../i18n'; @connect( state => ({ @@ -86,50 +87,50 @@ export default class MessageActions extends React.Component { if (nextProps.showActions !== this.props.showActions && nextProps.showActions) { const { actionMessage } = nextProps; // Cancel - this.options = ['Cancel']; + this.options = [I18n.t('Cancel')]; this.CANCEL_INDEX = 0; // Reply if (!this.isRoomReadOnly()) { - this.options.push('Reply'); + this.options.push(I18n.t('Reply')); this.REPLY_INDEX = this.options.length - 1; } // Edit if (this.allowEdit(nextProps)) { - this.options.push('Edit'); + this.options.push(I18n.t('Edit')); this.EDIT_INDEX = this.options.length - 1; } // Permalink - this.options.push('Copy Permalink'); + this.options.push(I18n.t('Copy_Permalink')); this.PERMALINK_INDEX = this.options.length - 1; // Copy - this.options.push('Copy Message'); + this.options.push(I18n.t('Copy_Message')); this.COPY_INDEX = this.options.length - 1; // Share - this.options.push('Share Message'); + this.options.push(I18n.t('Share_Message')); this.SHARE_INDEX = this.options.length - 1; // Quote if (!this.isRoomReadOnly()) { - this.options.push('Quote'); + this.options.push(I18n.t('Quote')); this.QUOTE_INDEX = this.options.length - 1; } // Star if (this.props.Message_AllowStarring) { - this.options.push(actionMessage.starred ? 'Unstar' : 'Star'); + this.options.push(I18n.t(actionMessage.starred ? 'Unstar' : 'Star')); this.STAR_INDEX = this.options.length - 1; } // Pin if (this.props.Message_AllowPinning) { - this.options.push(actionMessage.pinned ? 'Unpin' : 'Pin'); + this.options.push(I18n.t(actionMessage.pinned ? 'Unpin' : 'Pin')); this.PIN_INDEX = this.options.length - 1; } // Reaction if (!this.isRoomReadOnly() || this.canReactWhenReadOnly()) { - this.options.push('Add Reaction'); + this.options.push(I18n.t('Add_Reaction')); this.REACTION_INDEX = this.options.length - 1; } // Delete if (this.allowDelete(nextProps)) { - this.options.push('Delete'); + this.options.push(I18n.t('Delete')); this.DELETE_INDEX = this.options.length - 1; } setTimeout(() => { @@ -141,7 +142,7 @@ export default class MessageActions extends React.Component { if (this.state.copyPermalink) { this.setState({ copyPermalink: false }); await Clipboard.setString(nextProps.permalink); - showToast('Permalink copied to clipboard!'); + showToast(I18n.t('Permalink_copied_to_clipboard')); this.props.permalinkClear(); // quote } else if (this.state.quote) { @@ -234,15 +235,15 @@ export default class MessageActions extends React.Component { handleDelete() { Alert.alert( - 'Are you sure?', - 'You will not be able to recover this message!', + I18n.t('Are_you_sure_question_mark'), + I18n.t('You_will_not_be_able_to_recover_this_message'), [ { - text: 'Cancel', + text: I18n.t('Cancel'), style: 'cancel' }, { - text: 'Yes, delete it!', + text: I18n.t('Yes_action_it', { action: 'delete' }), style: 'destructive', onPress: () => this.props.deleteRequest(this.props.actionMessage) } @@ -258,7 +259,7 @@ export default class MessageActions extends React.Component { handleCopy = async() => { await Clipboard.setString(this.props.actionMessage.msg); - showToast('Copied to clipboard!'); + showToast(I18n.t('Copied_to_clipboard')); } handleShare = async() => { @@ -336,7 +337,7 @@ export default class MessageActions extends React.Component { return ( <ActionSheet ref={o => this.ActionSheet = o} - title='Messages actions' + title={I18n.t('Message_actions')} testID='message-actions' options={this.options} cancelButtonIndex={this.CANCEL_INDEX} diff --git a/app/containers/MessageBox/Recording.js b/app/containers/MessageBox/Recording.js index 713564a19c94d80267e70fa70a9324d44648a9f9..5396fb67184b57794e0c653dd1de8d243cb49e1f 100644 --- a/app/containers/MessageBox/Recording.js +++ b/app/containers/MessageBox/Recording.js @@ -4,6 +4,7 @@ import { View, SafeAreaView, Platform, PermissionsAndroid, Text } from 'react-na import { AudioRecorder, AudioUtils } from 'react-native-audio'; import Icon from 'react-native-vector-icons/MaterialIcons'; import styles from './styles'; +import I18n from '../../i18n'; export const _formatTime = function(seconds) { let minutes = Math.floor(seconds / 60); @@ -24,8 +25,8 @@ export default class extends React.PureComponent { } const rationale = { - title: 'Microphone Permission', - message: 'Rocket Chat needs access to your microphone so you can send audio message.' + title: I18n.t('Microphone_Permission'), + message: I18n.t('Microphone_Permission_Message') }; const result = await PermissionsAndroid.request(PermissionsAndroid.PERMISSIONS.RECORD_AUDIO, rationale); @@ -118,7 +119,7 @@ export default class extends React.PureComponent { style={[styles.actionButtons, { color: 'red' }]} name='clear' key='clear' - accessibilityLabel='Cancel recording' + accessibilityLabel={I18n.t('Cancel_recording')} accessibilityTraits='button' onPress={this.cancelAudioMessage} /> @@ -127,7 +128,7 @@ export default class extends React.PureComponent { style={[styles.actionButtons, { color: 'green' }]} name='check' key='check' - accessibilityLabel='Finish recording' + accessibilityLabel={I18n.t('Finish_recording')} accessibilityTraits='button' onPress={this.finishAudioMessage} /> diff --git a/app/containers/MessageBox/index.js b/app/containers/MessageBox/index.js index 113ce2dbe0718b3a0372c72eb100ce9d9af96df1..fea1c26b64d68fd50212a0618012ef4eaf4ba75c 100644 --- a/app/containers/MessageBox/index.js +++ b/app/containers/MessageBox/index.js @@ -19,6 +19,7 @@ import { emojis } from '../../emojis'; import Recording from './Recording'; import './EmojiKeyboard'; import log from '../../utils/log'; +import I18n from '../../i18n'; const MENTIONS_TRACKING_TYPE_USERS = '@'; const MENTIONS_TRACKING_TYPE_EMOJIS = ':'; @@ -107,7 +108,7 @@ export default class MessageBox extends React.PureComponent { return (<Icon style={styles.actionButtons} name='close' - accessibilityLabel='Cancel editing' + accessibilityLabel={I18n.t('Cancel_editing')} accessibilityTraits='button' onPress={() => this.editCancel()} testID='messagebox-cancel-editing' @@ -116,14 +117,14 @@ export default class MessageBox extends React.PureComponent { return !this.state.showEmojiKeyboard ? (<Icon style={styles.actionButtons} onPress={() => this.openEmoji()} - accessibilityLabel='Open emoji selector' + accessibilityLabel={I18n.t('Open_emoji_selector')} accessibilityTraits='button' name='mood' testID='messagebox-open-emoji' />) : (<Icon onPress={() => this.closeEmoji()} style={styles.actionButtons} - accessibilityLabel='Close emoji selector' + accessibilityLabel={I18n.t('Close_emoji_selector')} accessibilityTraits='button' name='keyboard' testID='messagebox-close-emoji' @@ -137,7 +138,7 @@ export default class MessageBox extends React.PureComponent { style={[styles.actionButtons, { color: '#1D74F5' }]} name='send' key='sendIcon' - accessibilityLabel='Send message' + accessibilityLabel={I18n.t('Send message')} accessibilityTraits='button' onPress={() => this.submit(this.state.text)} testID='messagebox-send-message' @@ -148,7 +149,7 @@ export default class MessageBox extends React.PureComponent { style={[styles.actionButtons, { color: '#1D74F5', paddingHorizontal: 10 }]} name='mic' key='micIcon' - accessibilityLabel='Send audio message' + accessibilityLabel={I18n.t('Send audio message')} accessibilityTraits='button' onPress={() => this.recordAudioMessage()} testID='messagebox-send-audio' @@ -157,7 +158,7 @@ export default class MessageBox extends React.PureComponent { style={[styles.actionButtons, { color: '#2F343D', fontSize: 16 }]} name='plus' key='fileIcon' - accessibilityLabel='Message actions' + accessibilityLabel={I18n.t('Message actions')} accessibilityTraits='button' onPress={() => this.addFile()} testID='messagebox-actions' @@ -169,18 +170,13 @@ export default class MessageBox extends React.PureComponent { const options = { maxHeight: 1960, maxWidth: 1960, - quality: 0.8, - customButtons: [{ - name: 'import', title: 'Import File From' - }] + quality: 0.8 }; ImagePicker.showImagePicker(options, (response) => { if (response.didCancel) { console.warn('User cancelled image picker'); } else if (response.error) { log('ImagePicker Error', response.error); - } else if (response.customButton) { - console.warn('User tapped custom button: ', response.customButton); } else { const fileInfo = { name: response.fileName, @@ -250,10 +246,10 @@ export default class MessageBox extends React.PureComponent { _getFixedMentions(keyword) { if ('all'.indexOf(keyword) !== -1) { - this.users = [{ _id: -1, username: 'all', desc: 'all' }, ...this.users]; + this.users = [{ _id: -1, username: 'all' }, ...this.users]; } if ('here'.indexOf(keyword) !== -1) { - this.users = [{ _id: -2, username: 'here', desc: 'active users' }, ...this.users]; + this.users = [{ _id: -2, username: 'here' }, ...this.users]; } } @@ -419,7 +415,7 @@ export default class MessageBox extends React.PureComponent { onPress={() => this._onPressMention(item)} > <Text style={styles.fixedMentionAvatar}>{item.username}</Text> - <Text>Notify {item.desc} in this room</Text> + <Text>{item.username === 'here' ? I18n.t('Notify_active_in_this_room') : I18n.t('Notify_all_in_this_room')}</Text> </TouchableOpacity> ) renderMentionEmoji = (item) => { @@ -507,7 +503,7 @@ export default class MessageBox extends React.PureComponent { returnKeyType='default' keyboardType='twitter' blurOnSubmit={false} - placeholder='New Message' + placeholder={I18n.t('New_Message')} onChangeText={text => this.onChangeText(text)} value={this.state.text} underlineColorAndroid='transparent' diff --git a/app/containers/MessageErrorActions.js b/app/containers/MessageErrorActions.js index a65c016d73ab4cbba558e7ac4f83ac2cb4de9f9c..4a8a9eeb5586c4e760b9df0a6a3dcc1c25013408 100644 --- a/app/containers/MessageErrorActions.js +++ b/app/containers/MessageErrorActions.js @@ -7,6 +7,7 @@ import { errorActionsHide } from '../actions/messages'; import RocketChat from '../lib/rocketchat'; import database from '../lib/realm'; import protectedFunction from '../lib/methods/helpers/protectedFunction'; +import I18n from '../i18n'; @connect( state => ({ @@ -27,7 +28,7 @@ export default class MessageErrorActions extends React.Component { constructor(props) { super(props); this.handleActionPress = this.handleActionPress.bind(this); - this.options = ['Cancel', 'Delete', 'Resend']; + this.options = [I18n.t('Cancel'), I18n.t('Delete'), I18n.t('Resend')]; this.CANCEL_INDEX = 0; this.DELETE_INDEX = 1; this.RESEND_INDEX = 2; @@ -66,7 +67,7 @@ export default class MessageErrorActions extends React.Component { return ( <ActionSheet ref={o => this.ActionSheet = o} - title='Messages actions' + title={I18n.t('Message_actions')} options={this.options} cancelButtonIndex={this.CANCEL_INDEX} destructiveButtonIndex={this.DELETE_INDEX} diff --git a/app/containers/Sidebar.js b/app/containers/Sidebar.js index df34b7ff12d77b4f79bdf1254e76ec4ae7660dfd..655338416291847376944ee9e82315ab05885e2a 100644 --- a/app/containers/Sidebar.js +++ b/app/containers/Sidebar.js @@ -7,6 +7,7 @@ import { DrawerActions } from 'react-navigation'; import database from '../lib/realm'; import { setServer } from '../actions/server'; import { logout } from '../actions/login'; +import I18n from '../i18n'; const styles = StyleSheet.create({ scrollView: { @@ -115,9 +116,7 @@ export default class Sidebar extends Component { testID='sidebar-logout' > <View style={styles.serverItem}> - <Text> - Logout - </Text> + <Text>{I18n.t('Logout')}</Text> </View> </TouchableHighlight> <TouchableHighlight @@ -128,9 +127,7 @@ export default class Sidebar extends Component { testID='sidebar-add-server' > <View style={styles.serverItem}> - <Text> - Add Server - </Text> + <Text>{I18n.t('Add_Server')}</Text> </View> </TouchableHighlight> </View> diff --git a/app/containers/Typing.js b/app/containers/Typing.js index eb667093b5858c77b7e41fe20748180fbc7a0d6b..162d462f09c018feee9af2728acb3894e14be9e4 100644 --- a/app/containers/Typing.js +++ b/app/containers/Typing.js @@ -2,6 +2,7 @@ import React from 'react'; import PropTypes from 'prop-types'; import { View, StyleSheet, Text, Keyboard, LayoutAnimation } from 'react-native'; import { connect } from 'react-redux'; +import I18n from '../i18n'; const styles = StyleSheet.create({ typing: { @@ -31,7 +32,7 @@ export default class Typing extends React.Component { } get usersTyping() { const users = this.props.usersTyping.filter(_username => this.props.username !== _username); - return users.length ? `${ users.join(' ,') } ${ users.length > 1 ? 'are' : 'is' } typing` : ''; + return users.length ? `${ users.join(' ,') } ${ users.length > 1 ? I18n.t('are_typing') : I18n.t('is_typing') }` : ''; } render() { const { usersTyping } = this; diff --git a/app/containers/message/ReactionsModal.js b/app/containers/message/ReactionsModal.js index 8f3f5620f1f59b719b589f113e2af78b44284423..cde6042605dd8ef7715e5bea7946ff90230a13b0 100644 --- a/app/containers/message/ReactionsModal.js +++ b/app/containers/message/ReactionsModal.js @@ -5,6 +5,7 @@ import Modal from 'react-native-modal'; import Icon from 'react-native-vector-icons/MaterialIcons'; import { connect } from 'react-redux'; import Emoji from './Emoji'; +import I18n from '../../i18n'; const styles = StyleSheet.create({ titleContainer: { @@ -68,11 +69,11 @@ export default class ReactionsModal extends React.PureComponent { renderItem = (item) => { const count = item.usernames.length; let usernames = item.usernames.slice(0, 3) - .map(username => (username.value === this.props.user.username ? 'you' : username.value)).join(', '); + .map(username => (username.value === this.props.user.username ? I18n.t('you') : username.value)).join(', '); if (count > 3) { - usernames = `${ usernames } and more ${ count - 3 }`; + usernames = `${ usernames } ${ I18n.t('and_more') } ${ count - 3 }`; } else { - usernames = usernames.replace(/,(?=[^,]*$)/, ' and'); + usernames = usernames.replace(/,(?=[^,]*$)/, ` ${ I18n.t('and') }`); } return ( <View style={styles.itemContainer}> @@ -86,7 +87,7 @@ export default class ReactionsModal extends React.PureComponent { </View> <View style={styles.peopleItemContainer}> <Text style={styles.reactCount}> - {count === 1 ? '1 person' : `${ count } people`} reacted + {count === 1 ? I18n.t('1_person_reacted') : I18n.t('N_people_reacted', { n: count })} </Text> <Text style={styles.peopleReacted}>{ usernames }</Text> </View> @@ -113,7 +114,7 @@ export default class ReactionsModal extends React.PureComponent { size={20} onPress={onClose} /> - <Text style={styles.title}>Reactions</Text> + <Text style={styles.title}>{I18n.t('Reactions')}</Text> </View> </TouchableWithoutFeedback> <View style={styles.listContainer}> diff --git a/app/containers/message/index.js b/app/containers/message/index.js index 3aac53d5f2c30bcfde863e225cda3570cf9883b3..54c31aa49bfa3f1951889c310599dc0ae9a7765f 100644 --- a/app/containers/message/index.js +++ b/app/containers/message/index.js @@ -21,6 +21,7 @@ import styles from './styles'; import { actionsShow, errorActionsShow, toggleReactionPicker, replyBroadcast } from '../../actions/messages'; import messagesStatus from '../../constants/messagesStatus'; import Touch from '../../utils/touch'; +import I18n from '../../i18n'; const SYSTEM_MESSAGES = [ 'r', @@ -44,35 +45,35 @@ const getInfoMessage = ({ t, role, msg, u }) => { if (t === 'rm') { - return 'Message removed'; + return I18n.t('Message_removed'); } else if (t === 'uj') { - return 'Has joined the channel.'; + return I18n.t('Has_joined_the_channel'); } else if (t === 'r') { - return `Room name changed to: ${ msg } by ${ u.username }`; + return I18n.t('Room_name_changed', { name: msg, userBy: u.username }); } else if (t === 'message_pinned') { - return 'Message pinned'; + return I18n.t('Message_pinned'); } else if (t === 'ul') { - return 'Has left the channel.'; + return I18n.t('Has_left_the_channel'); } else if (t === 'ru') { - return `User ${ msg } removed by ${ u.username }`; + return I18n.t('User_removed_by', { userRemoved: msg, userBy: u.username }); } else if (t === 'au') { - return `User ${ msg } added by ${ u.username }`; + return I18n.t('User_added_by', { userAdded: msg, userBy: u.username }); } else if (t === 'user-muted') { - return `User ${ msg } muted by ${ u.username }`; + return I18n.t('User_muted_by', { userMuted: msg, userBy: u.username }); } else if (t === 'user-unmuted') { - return `User ${ msg } unmuted by ${ u.username }`; + return I18n.t('User_unmuted_by', { userUnmuted: msg, userBy: u.username }); } else if (t === 'subscription-role-added') { return `${ msg } was set ${ role } by ${ u.username }`; } else if (t === 'subscription-role-removed') { return `${ msg } is no longer ${ role } by ${ u.username }`; } else if (t === 'room_changed_description') { - return `Room description changed to: ${ msg } by ${ u.username }`; + return I18n.t('Room_changed_description', { description: msg, userBy: u.username }); } else if (t === 'room_changed_announcement') { - return `Room announcement changed to: ${ msg } by ${ u.username }`; + return I18n.t('Room_changed_announcement', { announcement: msg, userBy: u.username }); } else if (t === 'room_changed_topic') { - return `Room topic changed to: ${ msg } by ${ u.username }`; + return I18n.t('Room_changed_topic', { topic: msg, userBy: u.username }); } else if (t === 'room_changed_privacy') { - return `Room type changed to: ${ msg } by ${ u.username }`; + return I18n.t('Room_changed_privacy', { type: msg, userBy: u.username }); } return ''; }; @@ -334,7 +335,7 @@ export default class Message extends React.Component { } = this.props; const username = item.alias || item.u.username; const isEditing = message._id === item._id && editing; - const accessibilityLabel = `Message from ${ username } at ${ moment(item.ts).format(this.timeFormat) }, ${ this.props.item.msg }`; + const accessibilityLabel = I18n.t('Message_accessibility', { user: username, time: moment(item.ts).format(this.timeFormat), message: this.props.item.msg }); return ( <Touch diff --git a/app/containers/routes/AuthRoutes.js b/app/containers/routes/AuthRoutes.js index c38082c5951b7463ad1beb1ade931071d82aeb82..cd46d1b4862e85a5224a7f3b5eb43b4591b0075e 100644 --- a/app/containers/routes/AuthRoutes.js +++ b/app/containers/routes/AuthRoutes.js @@ -17,6 +17,9 @@ import RoomFilesView from '../../views/RoomFilesView'; import RoomMembersView from '../../views/RoomMembersView'; import RoomInfoView from '../../views/RoomInfoView'; import RoomInfoEditView from '../../views/RoomInfoEditView'; +import I18n from '../../i18n'; + +const headerTintColor = '#292E35'; const AuthRoutes = createStackNavigator( { @@ -29,92 +32,92 @@ const AuthRoutes = createStackNavigator( CreateChannel: { screen: CreateChannelView, navigationOptions: { - title: 'Create Channel', - headerTintColor: '#292E35' + title: I18n.t('Create_Channel'), + headerTintColor } }, SelectedUsers: { screen: SelectedUsersView, navigationOptions: { - title: 'Select Users', - headerTintColor: '#292E35' + title: I18n.t('Select_Users'), + headerTintColor } }, AddServer: { screen: NewServerView, navigationOptions: { - title: 'New server', - headerTintColor: '#292E35' + title: I18n.t('New_Server'), + headerTintColor } }, RoomActions: { screen: RoomActionsView, navigationOptions: { - title: 'Actions', - headerTintColor: '#292E35' + title: I18n.t('Actions'), + headerTintColor } }, StarredMessages: { screen: StarredMessagesView, navigationOptions: { - title: 'Starred Messages', - headerTintColor: '#292E35' + title: I18n.t('Starred_Messages'), + headerTintColor } }, PinnedMessages: { screen: PinnedMessagesView, navigationOptions: { - title: 'Pinned Messages', - headerTintColor: '#292E35' + title: I18n.t('Pinned_Messages'), + headerTintColor } }, MentionedMessages: { screen: MentionedMessagesView, navigationOptions: { - title: 'Mentioned Messages', - headerTintColor: '#292E35' + title: I18n.t('Mentioned_Messages'), + headerTintColor } }, SnippetedMessages: { screen: SnippetedMessagesView, navigationOptions: { - title: 'Snippet Messages', - headerTintColor: '#292E35' + title: I18n.t('Snippet_Messages'), + headerTintColor } }, SearchMessages: { screen: SearchMessagesView, navigationOptions: { - title: 'Search Messages', - headerTintColor: '#292E35' + title: I18n.t('Search_Messages'), + headerTintColor } }, RoomFiles: { screen: RoomFilesView, navigationOptions: { - title: 'Room Files', - headerTintColor: '#292E35' + title: I18n.t('Room_Files'), + headerTintColor } }, RoomMembers: { screen: RoomMembersView, navigationOptions: { - title: 'Room Members', - headerTintColor: '#292E35' + title: I18n.t('Room_Members'), + headerTintColor } }, RoomInfo: { screen: RoomInfoView, navigationOptions: { - title: 'Room Info', - headerTintColor: '#292E35' + title: I18n.t('Room_Info'), + headerTintColor } }, RoomInfoEdit: { screen: RoomInfoEditView, navigationOptions: { - title: 'Room Info Edit', - headerTintColor: '#292E35' + title: I18n.t('Room_Info_Edit'), + headerTintColor } } }, diff --git a/app/containers/routes/PublicRoutes.js b/app/containers/routes/PublicRoutes.js index 99fe3a31468bffb2aab26e7a423f0a26662f8eea..bff89edfc2f8341354aa968a40689d7504df60f4 100644 --- a/app/containers/routes/PublicRoutes.js +++ b/app/containers/routes/PublicRoutes.js @@ -13,6 +13,7 @@ import TermsServiceView from '../../views/TermsServiceView'; import PrivacyPolicyView from '../../views/PrivacyPolicyView'; import ForgotPasswordView from '../../views/ForgotPasswordView'; import database from '../../lib/realm'; +import I18n from '../../i18n'; const hasServers = () => { const db = database.databases.serversDB.objects('servers'); @@ -24,12 +25,12 @@ const ServerStack = createStackNavigator({ screen: ListServerView, navigationOptions({ navigation }) { return { - title: 'Servers', + title: I18n.t('Servers'), headerRight: ( <TouchableOpacity onPress={() => navigation.navigate({ key: 'AddServer', routeName: 'AddServer' })} style={{ width: 50, alignItems: 'center' }} - accessibilityLabel='Add server' + accessibilityLabel={I18n.t('Add_Server')} accessibilityTraits='button' > <Icon name='plus' size={16} /> @@ -65,7 +66,7 @@ const LoginStack = createStackNavigator({ ForgotPassword: { screen: ForgotPasswordView, navigationOptions: { - title: 'Forgot my password', + title: I18n.t('Forgot_my_password'), headerTintColor: '#292E35' } } @@ -83,14 +84,14 @@ const RegisterStack = createStackNavigator({ TermsService: { screen: TermsServiceView, navigationOptions: { - title: 'Terms of service', + title: I18n.t('Terms_of_Service'), headerTintColor: '#292E35' } }, PrivacyPolicy: { screen: PrivacyPolicyView, navigationOptions: { - title: 'Privacy policy', + title: I18n.t('Privacy_Policy'), headerTintColor: '#292E35' } } diff --git a/app/i18n/index.js b/app/i18n/index.js new file mode 100644 index 0000000000000000000000000000000000000000..b130a36bd101dc71a65a551abb50dac08aded011 --- /dev/null +++ b/app/i18n/index.js @@ -0,0 +1,10 @@ +import I18n from 'react-native-i18n'; +import en from './locales/en'; + +I18n.fallbacks = true; + +I18n.translations = { + en +}; + +export default I18n; diff --git a/app/i18n/locales/en.js b/app/i18n/locales/en.js new file mode 100644 index 0000000000000000000000000000000000000000..e2cf96ecb69f93fde892c3773157523e49467423 --- /dev/null +++ b/app/i18n/locales/en.js @@ -0,0 +1,216 @@ +export default { + '1_online_member': '1 online member', + '1_person_reacted': '1 person reacted', + Actions: 'Actions', + Add_Reaction: 'Add Reaction', + Add_Server: 'Add Server', + Add_user: 'Add user', + Alert: 'Alert', + alert: 'alert', + alerts: 'alerts', + All_users_in_the_channel_can_write_new_messages: 'All users in the channel can write new messages', + All: 'All', + Allow_Reactions: 'Allow Reactions', + and_more: 'and more', + and: 'and', + announcement: 'announcement', + Announcement: 'Announcement', + ARCHIVE: 'ARCHIVE', + archive: 'archive', + are_typing: 'are typing', + Are_you_sure_question_mark: 'Are you sure?', + Are_you_sure_you_want_to_leave_the_room: 'Are you sure you want to leave the room {{room}}?', + Authenticating: 'Authenticating', + Away: 'Away', + Block_user: 'Block user', + Broadcast_channel_Description: 'Only authorized users can write new messages, but the other users will be able to reply', + Broadcast_Channel: 'Broadcast Channel', + Busy: 'Busy', + By_proceeding_you_are_agreeing: 'By proceeding you are agreeing to our', + Cancel_editing: 'Cancel editing', + Cancel_recording: 'Cancel recording', + Cancel: 'Cancel', + Channel_Name: 'Channel Name', + Close_emoji_selector: 'Close emoji selector', + Code: 'Code', + Colaborative: 'Colaborative', + Connect: 'Connect', + Connected_to: 'Connected to', + Connecting: 'Connecting', + Copied_to_clipboard: 'Copied to clipboard!', + Copy_Message: 'Copy Message', + Copy_Permalink: 'Copy Permalink', + Create_account: 'Create account', + Create_Channel: 'Create Channel', + Create: 'Create', + Delete_Room_Warning: 'Deleting a room will delete all messages posted within the room. This cannot be undone.', + delete: 'delete', + Delete: 'Delete', + DELETE: 'DELETE', + description: 'description', + Description: 'Description', + Disable_notifications: 'Disable notifications', + Do_you_really_want_to_key_this_room_question_mark: 'Do you really want to {{key}} this room?', + edit: 'edit', + Edit: 'Edit', + Email_or_password_field_is_empty: 'Email or password field is empty', + Email: 'Email', + Enable_notifications: 'Enable notifications', + Everyone_can_access_this_channel: 'Everyone can access this channel', + Files: 'Files', + Finish_recording: 'Finish recording', + Forgot_my_password: 'Forgot my password', + Forgot_password_If_this_email_is_registered: 'If this email is registered, we\'ll send instructions on how to reset your password. If you do not receive an email shortly, please come back and try again.', + Forgot_password: 'Forgot password', + Has_joined_the_channel: 'Has joined the channel', + Has_left_the_channel: 'Has left the channel', + I_have_an_account: 'I have an account', + Invisible: 'Invisible', + is_a_valid_RocketChat_instance: 'is a valid Rocket.Chat instance', + is_not_a_valid_RocketChat_instance: 'is not a valid Rocket.Chat instance', + is_typing: 'is typing', + Just_invited_people_can_access_this_channel: 'Just invited people can access this channel', + last_message: 'last message', + Leave_channel: 'Leave channel', + leave: 'leave', + Loading_messages_ellipsis: 'Loading messages...', + Login: 'Login', + Logout: 'Logout', + Members: 'Members', + Mentioned_Messages: 'Mentioned Messages', + mentioned: 'mentioned', + Mentions: 'Mentions', + Message_accessibility: 'Message from {{user}} at {{time}}: {{message}}', + Message_actions: 'Message actions', + Message_pinned: 'Message pinned', + Message_removed: 'Message removed', + Microphone_Permission_Message: 'Rocket Chat needs access to your microphone so you can send audio message.', + Microphone_Permission: 'Microphone Permission', + Mute: 'Mute', + muted: 'muted', + My_servers: 'My servers', + N_online_members: '{{n}} online members', + N_person_reacted: '{{n}} people reacted', + Name: 'Name', + New_in_RocketChat_question_mark: 'New in Rocket.Chat?', + New_Message: 'New Message', + New_Server: 'New Server', + No_files: 'No files', + No_mentioned_messages: 'No mentioned messages', + No_pinned_messages: 'No pinned messages', + No_snippeted_messages: 'No snippeted messages', + No_starred_messages: 'No starred messages', + No_announcement_provided: 'No announcement provided.', + No_description_provided: 'No description provided.', + No_topic_provided: 'No topic provided.', + No_Message: 'No Message', + No_Reactions: 'No Reactions', + Not_logged: 'Not logged', + Nothing_to_save: 'Nothing to save!', + Notify_active_in_this_room: 'Notify active users in this room', + Notify_all_in_this_room: 'Notify all in this room', + Offline: 'Offline', + Online: 'Online', + Only_authorized_users_can_write_new_messages: 'Only authorized users can write new messages', + Open_emoji_selector: 'Open emoji selector', + Or_continue_using_social_accounts: 'Or continue using social accounts', + Password: 'Password', + Permalink_copied_to_clipboard: 'Permalink copied to clipboard!', + Pin: 'Pin', + Pinned_Messages: 'Pinned Messages', + pinned: 'pinned', + Pinned: 'Pinned', + Privacy_Policy: ' Privacy Policy', + Private_Channel: 'Private Channel', + Private: 'Private', + Public_Channel: 'Public Channel', + Public: 'Public', + Quote: 'Quote', + Reactions_are_disabled: 'Reactions are disabled', + Reactions_are_enabled: 'Reactions are enabled', + Reactions: 'Reactions', + Read_Only_Channel: 'Read Only Channel', + Read_Only: 'Read Only', + Register: 'Register', + Repeat_Password: 'Repeat Password', + Reply: 'Reply', + Resend: 'Resend', + Reset_password: 'Reset password', + RESET: 'RESET', + Roles: 'Roles', + Room_actions: 'Room actions', + Room_changed_announcement: 'Room announcement changed to: {{announcement}} by {{userBy}}', + Room_changed_description: 'Room description changed to: {{description}} by {{userBy}}', + Room_changed_privacy: 'Room type changed to: {{type}} by {{userBy}}', + Room_changed_topic: 'Room topic changed to: {{topic}} by {{userBy}}', + Room_Files: 'Room Files', + Room_Info_Edit: 'Room Info Edit', + Room_Info: 'Room Info', + Room_Members: 'Room Members', + Room_name_changed: 'Room name changed to: {{name}} by {{userBy}}', + SAVE: 'SAVE', + Search_Messages: 'Search Messages', + Search: 'Search', + Select_Users: 'Select Users', + Send_audio_message: 'Send audio message', + Send_message: 'Send message', + Servers: 'Servers', + Settings_succesfully_changed: 'Settings succesfully changed!', + Share_Message: 'Share Message', + Share: 'Share', + Sign_in_your_server: 'Sign in your server', + Sign_Up: 'Sign Up', + Snippet_Messages: 'Snippet Messages', + snippeted: 'snippeted', + Snippets: 'Snippets', + Some_field_is_invalid_or_empty: 'Some field is invalid or empty', + Star_room: 'Star room', + Star: 'Star', + Starred_Messages: 'Starred Messages', + starred: 'starred', + Starred: 'Starred', + Start_of_conversation: 'Start of conversation', + Submit: 'Submit', + tap_to_change_status: 'tap to change status', + Tap_to_view_servers_list: 'Tap to view servers list', + Terms_of_Service: ' Terms of Service ', + There_was_an_error_while_saving_settings: 'There was an error while saving settings!', + This_room_is_blocked: 'This room is blocked', + This_room_is_read_only: 'This room is read only', + Timezone: 'Timezone', + topic: 'topic', + Topic: 'Topic', + Type_the_channel_name_here: 'Type the channel name here', + unarchive: 'unarchive', + UNARCHIVE: 'UNARCHIVE', + Unblock_user: 'Unblock user', + Unmute: 'Unmute', + unmuted: 'unmuted', + Unpin: 'Unpin', + unread_messages: 'unread messages', + Unstar: 'Unstar', + User_added_by: 'User {{userAdded}} added by {{userBy}}', + User_has_been_key: 'User has been {{key}}!', + User_is_no_longer_role_by_: '{{user}} is no longer {{role}} by {{userBy}}', + User_muted_by: 'User {{userMuted}} muted by {{userBy}}', + User_removed_by: 'User {{userRemoved}} removed by {{userBy}}', + User_unmuted_by: 'User {{userUnmuted}} unmuted by {{userBy}}', + User_was_set_role_by_: '{{user}} was set {{role}} by {{userBy}}', + Username_is_empty: 'Username is empty', + Username: 'Username', + Validating: 'Validating', + Video_call: 'Video call', + Voice_call: 'Voice call', + Welcome_title_pt_1: 'Prepare to take off with', + Welcome_title_pt_2: 'the ultimate chat platform', + Yes_action_it: 'Yes, {{action}} it!', + Yesterday: 'Yesterday', + You_are_in_preview_mode: 'You are in preview mode', + You_are_offline: 'You are offline', + You_can_search_using_RegExp_eg: 'You can search using RegExp. e.g. `/^text$/i`', + You_colon: 'You: ', + you_were_mentioned: 'you were mentioned', + You_will_not_be_able_to_recover_this_message: 'You will not be able to recover this message!', + you: 'you', + Your_server: 'Your server' +}; diff --git a/app/lib/methods/helpers/mergeSubscriptionsRooms.js b/app/lib/methods/helpers/mergeSubscriptionsRooms.js index e5d4340d44ba36e7b29407128d6e2137c0dd73cf..371ddaf3db77272cf2b682c942cb7b128143bc9c 100644 --- a/app/lib/methods/helpers/mergeSubscriptionsRooms.js +++ b/app/lib/methods/helpers/mergeSubscriptionsRooms.js @@ -2,7 +2,6 @@ import normalizeMessage from './normalizeMessage'; // TODO: delete and update export const merge = (subscription, room) => { - subscription.muted = []; if (room) { if (room.rid) { subscription.rid = room.rid; @@ -19,7 +18,9 @@ export const merge = (subscription, room) => { subscription.broadcast = room.broadcast; if (room.muted && room.muted.length) { - subscription.muted = room.muted.filter(user => user).map(user => ({ value: user })); + subscription.muted = room.muted.map(user => ({ value: user })); + } else { + subscription.muted = []; } } if (subscription.roles && subscription.roles.length) { diff --git a/app/presentation/RoomItem.js b/app/presentation/RoomItem.js index f2401d6983c4a61d445b727281dabfddb0f3cf40..243d4e3bee133bf655bd8c151b5b1430d6aba6bc 100644 --- a/app/presentation/RoomItem.js +++ b/app/presentation/RoomItem.js @@ -4,13 +4,13 @@ import PropTypes from 'prop-types'; import { View, Text, StyleSheet, ViewPropTypes } from 'react-native'; import Icon from 'react-native-vector-icons/MaterialCommunityIcons'; import { connect } from 'react-redux'; -// import SimpleMarkdown from 'simple-markdown'; import Avatar from '../containers/Avatar'; import Status from '../containers/status'; import Touch from '../utils/touch/index'; //eslint-disable-line import Markdown from '../containers/message/Markdown'; import RoomTypeIcon from '../containers/RoomTypeIcon'; +import I18n from '../i18n'; const styles = StyleSheet.create({ container: { @@ -98,36 +98,6 @@ const styles = StyleSheet.create({ marginTop: 3 } }); -// const markdownStyle = { block: { marginBottom: 0, flexWrap: 'wrap', flexDirection: 'row' } }; - -// const parseInline = (parse, content, state) => { -// const isCurrentlyInline = state.inline || false; -// state.inline = true; -// const result = parse(content, state); -// state.inline = isCurrentlyInline; -// return result; -// }; -// const parseCaptureInline = (capture, parse, state) => ({ content: parseInline(parse, capture[1], state) }); -// const customRules = { -// strong: { -// order: -4, -// match: SimpleMarkdown.inlineRegex(/^\*\*([\s\S]+?)\*\*(?!\*)/), -// parse: parseCaptureInline, -// react: (node, output, state) => ({ -// type: 'strong', -// key: state.key, -// props: { -// children: output(node.content, state) -// } -// }) -// }, -// text: { -// order: -3, -// match: SimpleMarkdown.inlineRegex(/^[\s\S]+?(?=[^0-9A-Za-z\s\u00c0-\uffff]|\n\n| {2,}\n|\w+:\S|$)/), -// parse: capture => ({ content: capture[0] }), -// react: node => node.content -// } -// }; const renderNumber = (unread, userMentions) => { if (!unread || unread <= 0) { @@ -207,13 +177,13 @@ export default class RoomItem extends React.Component { return ''; } if (!lastMessage) { - return 'No Message'; + return I18n.t('No_Message'); } let prefix = ''; if (lastMessage.u.username === this.props.user.username) { - prefix = 'You: '; + prefix = I18n.t('You_colon'); } else if (type !== 'd') { prefix = `${ lastMessage.u.username }: `; } @@ -234,7 +204,7 @@ export default class RoomItem extends React.Component { } formatDate = date => moment(date).calendar(null, { - lastDay: '[Yesterday]', + lastDay: `[${ I18n.t('Yesterday') }]`, sameDay: 'h:mm A', lastWeek: 'dddd', sameElse: 'MMM D' @@ -249,17 +219,17 @@ export default class RoomItem extends React.Component { let accessibilityLabel = name; if (unread === 1) { - accessibilityLabel += `, ${ unread } alert`; + accessibilityLabel += `, ${ unread } ${ I18n.t('alert') }`; } else if (unread > 1) { - accessibilityLabel += `, ${ unread } alerts`; + accessibilityLabel += `, ${ unread } ${ I18n.t('alerts') }`; } if (userMentions > 0) { - accessibilityLabel += ', you were mentioned'; + accessibilityLabel += `, ${ I18n.t('you_were_mentioned') }`; } if (date) { - accessibilityLabel += `, last message ${ date }`; + accessibilityLabel += `, ${ I18n.t('last_message') } ${ date }`; } return ( diff --git a/app/sagas/messages.js b/app/sagas/messages.js index 0585e833b4f16cd752fc6de18cfa31e4215e0081..0dff63c6906b69d148f039094c1c4fc4b06b4121 100644 --- a/app/sagas/messages.js +++ b/app/sagas/messages.js @@ -98,7 +98,7 @@ const handleReplyBroadcast = function* handleReplyBroadcast({ message }) { } yield delay(100); const server = yield select(state => state.server.server); - const msg = `[ ](${ server }/direct/${ username }?msg=${ message._id })`; + const msg = `[ ](${ server }/direct/${ username }?msg=${ message._id }) `; yield put(setInput({ msg })); } catch (e) { log('handleReplyBroadcast', e); diff --git a/app/views/CreateChannelView.js b/app/views/CreateChannelView.js index ed221fe3ef0babe78b7c6e2e90d92968e941f606..bad0fcfad41cc7a845f3631a51206c43464e1ad6 100644 --- a/app/views/CreateChannelView.js +++ b/app/views/CreateChannelView.js @@ -11,6 +11,7 @@ import styles from './Styles'; import KeyboardView from '../presentation/KeyboardView'; import scrollPersistTaps from '../utils/scrollPersistTaps'; import Button from '../containers/Button'; +import I18n from '../i18n'; @connect( state => ({ @@ -22,9 +23,6 @@ import Button from '../containers/Button'; }) ) export default class CreateChannelView extends LoggedView { - static navigationOptions = () => ({ - title: 'Create a New Channel' - }); static propTypes = { create: PropTypes.func.isRequired, createChannel: PropTypes.object.isRequired, @@ -99,8 +97,8 @@ export default class CreateChannelView extends LoggedView { return this.renderSwitch({ id: 'type', value: type, - label: type ? 'Private Channel' : 'Public Channel', - description: type ? 'Just invited people can access this channel' : 'Everyone can access this channel', + label: type ? I18n.t('Private_Channel') : I18n.t('Public_Channel'), + description: type ? I18n.t('Just_invited_people_can_access_this_channel') : I18n.t('Everyone_can_access_this_channel'), onValueChange: value => this.setState({ type: value }) }); } @@ -110,8 +108,8 @@ export default class CreateChannelView extends LoggedView { return this.renderSwitch({ id: 'readonly', value: readOnly, - label: 'Read Only Channel', - description: readOnly ? 'Only authorized users can write new messages' : 'All users in the channel can write new messages', + label: I18n.t('Read_Only_Channel'), + description: readOnly ? I18n.t('Only_authorized_users_can_write_new_messages') : I18n.t('All_users_in_the_channel_can_write_new_messages'), onValueChange: value => this.setState({ readOnly: value }), disabled: broadcast }); @@ -122,8 +120,8 @@ export default class CreateChannelView extends LoggedView { return this.renderSwitch({ id: 'broadcast', value: broadcast, - label: 'Broadcast Channel', - description: 'Only authorized users can write new messages, but the other users will be able to reply', + label: I18n.t('Broadcast_Channel'), + description: I18n.t('Broadcast_channel_Description'), onValueChange: (value) => { this.setState({ broadcast: value, @@ -142,10 +140,10 @@ export default class CreateChannelView extends LoggedView { <ScrollView {...scrollPersistTaps} contentContainerStyle={styles.containerScrollView}> <SafeAreaView testID='create-channel-view'> <RCTextInput - label='Channel Name' + label={I18n.t('Channel_Name')} value={this.state.channelName} onChangeText={channelName => this.setState({ channelName })} - placeholder='Type the channel name here' + placeholder={I18n.t('Type_the_channel_name_here')} returnKeyType='done' autoFocus testID='create-channel-name' @@ -156,7 +154,7 @@ export default class CreateChannelView extends LoggedView { {this.renderBroadcast()} <View style={styles.alignItemsFlexStart}> <Button - title='Create' + title={I18n.t('Create')} type='primary' onPress={this.submit} disabled={this.state.channelName.length === 0 || this.props.createChannel.isFetching} diff --git a/app/views/ForgotPasswordView.js b/app/views/ForgotPasswordView.js index b702ea25801247e8dda99fb10bfbbafa62d4d3e6..09d3170955f50a2f748f411a3a940c1990eac9c0 100644 --- a/app/views/ForgotPasswordView.js +++ b/app/views/ForgotPasswordView.js @@ -12,6 +12,7 @@ import Loading from '../containers/Loading'; import styles from './Styles'; import { showErrorAlert } from '../utils/info'; import scrollPersistTaps from '../utils/scrollPersistTaps'; +import I18n from '../i18n'; @connect(state => ({ login: state.login @@ -45,12 +46,7 @@ export default class ForgotPasswordView extends LoggedView { if (login.success) { this.props.navigation.goBack(); setTimeout(() => { - showErrorAlert( - 'If this email is registered, ' + - 'we\'ll send instructions on how to reset your password. ' + - 'If you do not receive an email shortly, please come back and try again.', - 'Alert' - ); + showErrorAlert(I18n.t('Forgot_password_If_this_email_is_registered'), I18n.t('Alert')); }); } } @@ -85,8 +81,8 @@ export default class ForgotPasswordView extends LoggedView { <View style={styles.formContainer}> <TextInput inputStyle={this.state.invalidEmail ? { borderColor: 'red' } : {}} - label='Email' - placeholder='Email' + label={I18n.t('Email')} + placeholder={I18n.t('Email')} keyboardType='email-address' returnKeyType='next' onChangeText={email => this.validate(email)} @@ -96,7 +92,7 @@ export default class ForgotPasswordView extends LoggedView { <View style={styles.alignItemsFlexStart}> <Button - title='Reset password' + title={I18n.t('Reset_password')} type='primary' onPress={this.resetPassword} testID='forgot-password-view-submit' diff --git a/app/views/ListServerView.js b/app/views/ListServerView.js index 8dd90583820327f084698f78654b14caedb11cd6..aee2ea6e6ee3b8c4188166bccfea7bceeb2f65b9 100644 --- a/app/views/ListServerView.js +++ b/app/views/ListServerView.js @@ -12,6 +12,7 @@ import { setServer } from '../actions/server'; import database from '../lib/realm'; import Fade from '../animations/fade'; import Touch from '../utils/touch'; +import I18n from '../i18n'; const styles = StyleSheet.create({ view: { @@ -141,7 +142,7 @@ class ListServerView extends LoggedView { getState = () => { const sections = [{ - title: 'My servers', + title: I18n.t('My_servers'), data: this.data }]; // diff --git a/app/views/LoginSignupView.js b/app/views/LoginSignupView.js index fab99301173475014efec90425ea7b2c5bc7c021..2cd391dcc6efd44b1a8cc8c0e57825d308661602 100644 --- a/app/views/LoginSignupView.js +++ b/app/views/LoginSignupView.js @@ -15,6 +15,7 @@ import scrollPersistTaps from '../utils/scrollPersistTaps'; import random from '../utils/random'; import Button from '../containers/Button'; import Loading from '../containers/Loading'; +import I18n from '../i18n'; const userAgentAndroid = 'Mozilla/5.0 (iPhone; CPU iPhone OS 10_3_1 like Mac OS X) AppleWebKit/603.1.30 (KHTML, like Gecko) Version/10.0 Mobile/14E304 Safari/602.1'; const userAgent = Platform.OS === 'ios' ? 'UserAgent' : userAgentAndroid; @@ -205,7 +206,7 @@ export default class LoginSignupView extends LoggedView { return ( <View style={styles.servicesContainer}> <Text style={styles.servicesTitle}> - Or continue using Social accounts + {I18n.t('Or_continue_using_social_accounts')} </Text> <View style={sharedStyles.loginOAuthButtons} key='services'> {this.props.Accounts_OAuth_Facebook && this.props.services.facebook && @@ -284,20 +285,20 @@ export default class LoginSignupView extends LoggedView { style={sharedStyles.loginLogo} resizeMode='center' /> - <Text style={[sharedStyles.loginText, styles.header, { color: '#81848A' }]}>Prepare to take off with</Text> - <Text style={[sharedStyles.loginText, styles.header]}>the ultimate chat platform</Text> + <Text style={[sharedStyles.loginText, styles.header, { color: '#81848A' }]}>{I18n.t('Welcome_title_pt_1')}</Text> + <Text style={[sharedStyles.loginText, styles.header]}>{I18n.t('Welcome_title_pt_2')}</Text> <Image style={styles.planetImage} source={require('../static/images/planet.png')} /> <Button - title='I have an account' + title={I18n.t('I_have_an_account')} type='primary' onPress={() => this.props.navigation.navigate({ key: 'Login', routeName: 'Login' })} testID='welcome-view-login' /> <Button - title='Create account' + title={I18n.t('Create_account')} type='secondary' onPress={() => this.props.navigation.navigate({ key: 'Register', routeName: 'Register' })} testID='welcome-view-register' diff --git a/app/views/LoginView.js b/app/views/LoginView.js index 0ee6de0d175b74b54457fb18a81980f7461c148a..25b29853df683d00c1e2bf711e7103f72cf82f03 100644 --- a/app/views/LoginView.js +++ b/app/views/LoginView.js @@ -15,6 +15,7 @@ import scrollPersistTaps from '../utils/scrollPersistTaps'; import { showToast } from '../utils/info'; import { COLOR_BUTTON_PRIMARY } from '../constants/colors'; import LoggedView from './View'; +import I18n from '../i18n'; @connect(state => ({ server: state.server.server, @@ -44,7 +45,7 @@ export default class LoginView extends LoggedView { submit = async() => { const { username, password, code } = this.state; if (username.trim() === '' || password.trim() === '') { - showToast('Email or password field is empty'); + showToast(I18n.t('Email_or_password_field_is_empty')); return; } Keyboard.dismiss(); @@ -62,9 +63,9 @@ export default class LoginView extends LoggedView { return ( <TextInput inputRef={ref => this.codeInput = ref} - label='Code' + label={I18n.t('Code')} onChangeText={code => this.setState({ code })} - placeholder='Code' + placeholder={I18n.t('Code')} keyboardType='numeric' returnKeyType='done' autoCapitalize='none' @@ -87,8 +88,8 @@ export default class LoginView extends LoggedView { <CloseModalButton navigation={this.props.navigation} /> <Text style={[styles.loginText, styles.loginTitle]}>Login</Text> <TextInput - label='Username' - placeholder={this.props.Accounts_EmailOrUsernamePlaceholder || 'Username'} + label={I18n.t('Username')} + placeholder={this.props.Accounts_EmailOrUsernamePlaceholder || I18n.t('Username')} keyboardType='email-address' returnKeyType='next' iconLeft='at' @@ -99,8 +100,8 @@ export default class LoginView extends LoggedView { <TextInput inputRef={(e) => { this.password = e; }} - label='Password' - placeholder={this.props.Accounts_PasswordPlaceholder || 'Password'} + label={I18n.t('Password')} + placeholder={this.props.Accounts_PasswordPlaceholder || I18n.t('Password')} returnKeyType='done' iconLeft='key-variant' secureTextEntry @@ -113,7 +114,7 @@ export default class LoginView extends LoggedView { <View style={styles.alignItemsFlexStart}> <Button - title='Login' + title={I18n.t('Login')} type='primary' onPress={this.submit} testID='login-view-submit' @@ -122,15 +123,15 @@ export default class LoginView extends LoggedView { style={[styles.loginText, { marginTop: 10 }]} testID='login-view-register' onPress={() => this.props.navigation.navigate('Register')} - >New in Rocket.Chat? - <Text style={{ color: COLOR_BUTTON_PRIMARY }}>Sign Up + >{I18n.t('New_in_RocketChat_question_mark')} + <Text style={{ color: COLOR_BUTTON_PRIMARY }}>{I18n.t('Sign_Up')} </Text> </Text> <Text style={[styles.loginText, { marginTop: 20, fontSize: 13 }]} onPress={() => this.props.navigation.navigate('ForgotPassword')} testID='login-view-forgot-password' - >Forgot password + >{I18n.t('Forgot_password')} </Text> </View> diff --git a/app/views/MentionedMessagesView/index.js b/app/views/MentionedMessagesView/index.js index b6add27999843b459a9406e154d24b26bb5f4b9f..226153cb1d8eca5e8ac5b84aa3058abfb6e2cc8a 100644 --- a/app/views/MentionedMessagesView/index.js +++ b/app/views/MentionedMessagesView/index.js @@ -8,6 +8,7 @@ import { openMentionedMessages, closeMentionedMessages } from '../../actions/men import styles from './styles'; import Message from '../../containers/message'; import RCActivityIndicator from '../../containers/ActivityIndicator'; +import I18n from '../../i18n'; @connect( state => ({ @@ -74,7 +75,7 @@ export default class MentionedMessagesView extends LoggedView { renderEmpty = () => ( <View style={styles.listEmptyContainer} testID='mentioned-messages-view'> - <Text>No mentioned messages</Text> + <Text>{I18n.t('No_mentioned_messages')}</Text> </View> ) diff --git a/app/views/NewServerView.js b/app/views/NewServerView.js index 28562495cb6f3410bebf55814b23b1e32f107871..2b3906fd58c9b1b1fcabbe529724f22b8c1b1d6b 100644 --- a/app/views/NewServerView.js +++ b/app/views/NewServerView.js @@ -11,6 +11,7 @@ import Button from '../containers/Button'; import TextInput from '../containers/TextInput'; import Loading from '../containers/Loading'; import LoggedView from './View'; +import I18n from '../i18n'; @connect(state => ({ validInstance: !state.server.failure && !state.server.connecting, @@ -81,7 +82,7 @@ export default class NewServerView extends LoggedView { if (this.props.validating) { return ( <Text style={[styles.validateText, styles.validatingText]}> - Validating {this.state.text || 'open'} ... + {I18n.t('Validating')} {this.state.text || 'open'} ... </Text> ); } @@ -89,13 +90,13 @@ export default class NewServerView extends LoggedView { if (this.props.validInstance) { return ( <Text style={[styles.validateText, styles.validText]}> - {this.state.url} is a valid Rocket.Chat instance + {this.state.url} {I18n.t('is_a_valid_RocketChat_instance')} </Text> ); } return ( <Text style={[styles.validateText, styles.invalidText]}> - {this.state.url} is not a valid Rocket.Chat instance + {this.state.url} {I18n.t('is_not_a_valid_RocketChat_instance')} </Text> ); } @@ -109,11 +110,11 @@ export default class NewServerView extends LoggedView { > <ScrollView {...scrollPersistTaps} contentContainerStyle={styles.containerScrollView}> <SafeAreaView testID='new-server-view'> - <Text style={[styles.loginText, styles.loginTitle]}>Sign in your server</Text> + <Text style={[styles.loginText, styles.loginTitle]}>{I18n.t('Sign_in_your_server')}</Text> <TextInput inputRef={e => this.input = e} containerStyle={{ marginBottom: 5 }} - label='Your server' + label={I18n.t('Your_server')} placeholder={this.state.defaultServer} returnKeyType='done' onChangeText={this.onChangeText} @@ -123,7 +124,7 @@ export default class NewServerView extends LoggedView { {this.renderValidation()} <View style={[styles.alignItemsFlexStart, { marginTop: 20 }]}> <Button - title='Connect' + title={I18n.t('Connect')} type='primary' onPress={this.submit} disabled={!validInstance} diff --git a/app/views/PinnedMessagesView/index.js b/app/views/PinnedMessagesView/index.js index 5a96b380fe2d9f70a546a3a8207b7c29c2f8be88..107be23b080d73753c584da38114fcdbaaad98c8 100644 --- a/app/views/PinnedMessagesView/index.js +++ b/app/views/PinnedMessagesView/index.js @@ -10,10 +10,11 @@ import styles from './styles'; import Message from '../../containers/message'; import { togglePinRequest } from '../../actions/messages'; import RCActivityIndicator from '../../containers/ActivityIndicator'; +import I18n from '../../i18n'; const PIN_INDEX = 0; const CANCEL_INDEX = 1; -const options = ['Unpin', 'Cancel']; +const options = [I18n.t('Unpin'), I18n.t('Cancel')]; @connect( state => ({ @@ -98,7 +99,7 @@ export default class PinnedMessagesView extends LoggedView { renderEmpty = () => ( <View style={styles.listEmptyContainer} testID='pinned-messages-view'> - <Text>No pinned messages</Text> + <Text>{I18n.t('No_pinned_messages')}</Text> </View> ) @@ -138,7 +139,7 @@ export default class PinnedMessagesView extends LoggedView { <ActionSheet key='pinned-messages-view-action-sheet' ref={o => this.actionSheet = o} - title='Actions' + title={I18n.t('Actions')} options={options} cancelButtonIndex={CANCEL_INDEX} onPress={this.handleActionPress} diff --git a/app/views/PrivacyPolicyView.js b/app/views/PrivacyPolicyView.js index e4f96abc18701cbae0866c7758225a9d379de10f..8a78a70b3de214b88725fea17c977c05eff22a45 100644 --- a/app/views/PrivacyPolicyView.js +++ b/app/views/PrivacyPolicyView.js @@ -3,15 +3,11 @@ import PropTypes from 'prop-types'; import { WebView } from 'react-native'; import { connect } from 'react-redux'; -class PrivacyPolicyView extends React.Component { +class PrivacyPolicyView extends React.PureComponent { static propTypes = { privacyPolicy: PropTypes.string } - static navigationOptions = () => ({ - title: 'Terms of service' - }); - render() { return ( <WebView source={{ html: this.props.privacyPolicy }} /> diff --git a/app/views/RegisterView.js b/app/views/RegisterView.js index 364572fe4e1999e4da823cdf77b9b4d7c8ba6115..caeb72613f833f66aba5b948dd193f3fcf17a7fd 100644 --- a/app/views/RegisterView.js +++ b/app/views/RegisterView.js @@ -13,6 +13,7 @@ import { showToast } from '../utils/info'; import CloseModalButton from '../containers/CloseModalButton'; import scrollPersistTaps from '../utils/scrollPersistTaps'; import LoggedView from './View'; +import I18n from '../i18n'; @connect(state => ({ server: state.server.server, @@ -65,7 +66,7 @@ export default class RegisterView extends LoggedView { name, email, password, code } = this.state; if (!this.valid()) { - showToast('Some field is invalid or empty'); + showToast(I18n.t('Some_field_is_invalid_or_empty')); return; } @@ -78,7 +79,7 @@ export default class RegisterView extends LoggedView { usernameSubmit = () => { const { username } = this.state; if (!username) { - showToast('Username is empty'); + showToast(I18n.t('Username_is_empty')); return; } @@ -102,8 +103,8 @@ export default class RegisterView extends LoggedView { <View> <TextInput inputRef={(e) => { this.name = e; }} - label={this.props.Accounts_NamePlaceholder || 'Name'} - placeholder={this.props.Accounts_NamePlaceholder || 'Name'} + label={this.props.Accounts_NamePlaceholder || I18n.t('Name')} + placeholder={this.props.Accounts_NamePlaceholder || I18n.t('Name')} returnKeyType='next' iconLeft='account' onChangeText={name => this.setState({ name })} @@ -112,8 +113,8 @@ export default class RegisterView extends LoggedView { /> <TextInput inputRef={(e) => { this.email = e; }} - label={this.props.Accounts_EmailOrUsernamePlaceholder || 'Email'} - placeholder={this.props.Accounts_EmailOrUsernamePlaceholder || 'Email'} + label={this.props.Accounts_EmailOrUsernamePlaceholder || I18n.t('Email')} + placeholder={this.props.Accounts_EmailOrUsernamePlaceholder || I18n.t('Email')} returnKeyType='next' keyboardType='email-address' iconLeft='email' @@ -124,8 +125,8 @@ export default class RegisterView extends LoggedView { /> <TextInput inputRef={(e) => { this.password = e; }} - label={this.props.Accounts_PasswordPlaceholder || 'Password'} - placeholder={this.props.Accounts_PasswordPlaceholder || 'Password'} + label={this.props.Accounts_PasswordPlaceholder || I18n.t('Password')} + placeholder={this.props.Accounts_PasswordPlaceholder || I18n.t('Password')} returnKeyType='next' iconLeft='key-variant' secureTextEntry @@ -140,8 +141,8 @@ export default class RegisterView extends LoggedView { this.state.confirmPassword && this.state.confirmPassword !== this.state.password ? { borderColor: 'red' } : {} } - label={this.props.Accounts_RepeatPasswordPlaceholder || 'Repeat Password'} - placeholder={this.props.Accounts_RepeatPasswordPlaceholder || 'Repeat Password'} + label={this.props.Accounts_RepeatPasswordPlaceholder || I18n.t('Repeat_Password')} + placeholder={this.props.Accounts_RepeatPasswordPlaceholder || I18n.t('Repeat_Password')} returnKeyType='done' iconLeft='key-variant' secureTextEntry @@ -152,13 +153,13 @@ export default class RegisterView extends LoggedView { <View style={styles.alignItemsFlexStart}> <Text style={styles.loginTermsText}> - By proceeding you are agreeing to our - <Text style={styles.link} onPress={this.termsService}> Terms of Service </Text> - and - <Text style={styles.link} onPress={this.privacyPolicy}> Privacy Policy</Text> + {I18n.t('By_proceeding_you_are_agreeing')} + <Text style={styles.link} onPress={this.termsService}>{I18n.t('Terms_of_Service')}</Text> + {I18n.t('and')} + <Text style={styles.link} onPress={this.privacyPolicy}>{I18n.t('Privacy_Policy')}</Text> </Text> <Button - title='Register' + title={I18n.t('Register')} type='primary' onPress={this.submit} testID='register-view-submit' @@ -176,8 +177,8 @@ export default class RegisterView extends LoggedView { <View> <TextInput inputRef={(e) => { this.username = e; }} - label={this.props.Accounts_UsernamePlaceholder || 'Username'} - placeholder={this.props.Accounts_UsernamePlaceholder || 'Username'} + label={this.props.Accounts_UsernamePlaceholder || I18n.t('Username')} + placeholder={this.props.Accounts_UsernamePlaceholder || I18n.t('Username')} returnKeyType='done' iconLeft='at' onChangeText={username => this.setState({ username })} @@ -187,7 +188,7 @@ export default class RegisterView extends LoggedView { <View style={styles.alignItemsFlexStart}> <Button - title='Register' + title={I18n.t('Register')} type='primary' onPress={this.usernameSubmit} testID='register-view-submit-username' @@ -203,7 +204,7 @@ export default class RegisterView extends LoggedView { <ScrollView {...scrollPersistTaps} contentContainerStyle={styles.containerScrollView}> <SafeAreaView testID='register-view'> <CloseModalButton navigation={this.props.navigation} /> - <Text style={[styles.loginText, styles.loginTitle]}>Sign Up</Text> + <Text style={[styles.loginText, styles.loginTitle]}>{I18n.t('Sign_Up')}</Text> {this._renderRegister()} {this._renderUsername()} {this.props.login.failure && diff --git a/app/views/RoomActionsView/index.js b/app/views/RoomActionsView/index.js index 02b31881d666a7f532d6471f35ce228a95762389..52aef0c93708337e0f1c99523e36e13395aac0ba 100644 --- a/app/views/RoomActionsView/index.js +++ b/app/views/RoomActionsView/index.js @@ -17,6 +17,7 @@ import { leaveRoom } from '../../actions/room'; import { setLoading } from '../../actions/selectedUsers'; import log from '../../utils/log'; import RoomTypeIcon from '../../containers/RoomTypeIcon'; +import I18n from '../../i18n'; const renderSeparator = () => <View style={styles.separator} />; const getRoomTitle = room => (room.t === 'd' ? <Text>{room.fname}</Text> : <Text><RoomTypeIcon type={room.t} /> {room.name}</Text>); @@ -158,13 +159,13 @@ export default class RoomActionsView extends LoggedView { data: [ { icon: 'ios-call-outline', - name: 'Voice call', + name: I18n.t('Voice_call'), disabled: true, testID: 'room-actions-voice' }, { icon: 'ios-videocam-outline', - name: 'Video call', + name: I18n.t('Video_call'), disabled: true, testID: 'room-actions-video' } @@ -174,55 +175,55 @@ export default class RoomActionsView extends LoggedView { data: [ { icon: 'ios-attach', - name: 'Files', + name: I18n.t('Files'), route: 'RoomFiles', params: { rid }, testID: 'room-actions-files' }, { icon: 'ios-at-outline', - name: 'Mentions', + name: I18n.t('Mentions'), route: 'MentionedMessages', params: { rid }, testID: 'room-actions-mentioned' }, { icon: 'ios-star-outline', - name: 'Starred', + name: I18n.t('Starred'), route: 'StarredMessages', params: { rid }, testID: 'room-actions-starred' }, { icon: 'ios-search', - name: 'Search', + name: I18n.t('Search'), route: 'SearchMessages', params: { rid }, testID: 'room-actions-search' }, { icon: 'ios-share-outline', - name: 'Share', + name: I18n.t('Share'), disabled: true, testID: 'room-actions-share' }, { icon: 'ios-pin', - name: 'Pinned', + name: I18n.t('Pinned'), route: 'PinnedMessages', params: { rid }, testID: 'room-actions-pinned' }, { icon: 'ios-code', - name: 'Snippets', + name: I18n.t('Snippets'), route: 'SnippetedMessages', params: { rid }, testID: 'room-actions-snippeted' }, { icon: `ios-notifications${ notifications ? '' : '-off' }-outline`, - name: `${ notifications ? 'Enable' : 'Disable' } notifications`, + name: I18n.t(`${ notifications ? 'Enable' : 'Disable' }_notifications`), event: () => this.toggleNotifications(), testID: 'room-actions-notifications' } @@ -235,7 +236,7 @@ export default class RoomActionsView extends LoggedView { data: [ { icon: 'block', - name: `${ blocker ? 'Unblock' : 'Block' } user`, + name: I18n.t(`${ blocker ? 'Unblock' : 'Block' }_user`), type: 'danger', event: () => this.toggleBlockUser(), testID: 'room-actions-block-user' @@ -249,8 +250,10 @@ export default class RoomActionsView extends LoggedView { if (this.canViewMembers) { actions.push({ icon: 'ios-people', - name: 'Members', - description: (onlineMembers.length === 1 ? `${ onlineMembers.length } member` : `${ onlineMembers.length } members`), + name: I18n.t('Members'), + description: (onlineMembers.length === 1 ? + I18n.t('1_online_member') : + I18n.t('N_online_members', { n: onlineMembers.length })), route: 'RoomMembers', params: { rid, members: onlineMembers }, testID: 'room-actions-members' @@ -260,7 +263,7 @@ export default class RoomActionsView extends LoggedView { if (this.canAddUser) { actions.push({ icon: 'ios-person-add', - name: 'Add user', + name: I18n.t('Add_user'), route: 'SelectedUsers', params: { nextAction: async() => { @@ -283,7 +286,7 @@ export default class RoomActionsView extends LoggedView { data: [ { icon: 'block', - name: 'Leave channel', + name: I18n.t('Leave_channel'), type: 'danger', event: () => this.leaveChannel(), testID: 'room-actions-leave-channel' @@ -308,15 +311,15 @@ export default class RoomActionsView extends LoggedView { leaveChannel = () => { const { room } = this.state; Alert.alert( - 'Are you sure?', - `Are you sure you want to leave the room ${ getRoomTitle(room) }?`, + I18n.t('Are_you_sure_question_mark'), + I18n.t('Are_you_sure_you_want_to_leave_the_room', { room: room.t === 'd' ? room.fname : room.name }), [ { - text: 'Cancel', + text: I18n.t('Cancel'), style: 'cancel' }, { - text: 'Yes, leave it!', + text: I18n.t('Yes_action_it', { action: I18n.t('leave') }), style: 'destructive', onPress: () => this.props.leaveRoom(room.rid) } diff --git a/app/views/RoomFilesView/index.js b/app/views/RoomFilesView/index.js index 3061a32c000a83e1f07a9db5841461de04f73d4c..8a4941cbd48df761da4b3bb4e6dec12dbf8fb8a1 100644 --- a/app/views/RoomFilesView/index.js +++ b/app/views/RoomFilesView/index.js @@ -8,6 +8,7 @@ import { openRoomFiles, closeRoomFiles } from '../../actions/roomFiles'; import styles from './styles'; import Message from '../../containers/message'; import RCActivityIndicator from '../../containers/ActivityIndicator'; +import I18n from '../../i18n'; @connect( state => ({ @@ -74,7 +75,7 @@ export default class RoomFilesView extends LoggedView { renderEmpty = () => ( <View style={styles.listEmptyContainer} testID='room-files-view'> - <Text>No files</Text> + <Text>{I18n.t('No_files')}</Text> </View> ) diff --git a/app/views/RoomInfoEditView/index.js b/app/views/RoomInfoEditView/index.js index cd98cae9afffd0fa855e645a1a416dfdbe6688ea..95effd731d3cb6f9c33cc58972b4da00dc71045b 100644 --- a/app/views/RoomInfoEditView/index.js +++ b/app/views/RoomInfoEditView/index.js @@ -17,6 +17,7 @@ import Loading from '../../containers/Loading'; import SwitchContainer from './SwitchContainer'; import random from '../../utils/random'; import log from '../../utils/log'; +import I18n from '../../i18n'; const PERMISSION_SET_READONLY = 'set-readonly'; const PERMISSION_SET_REACT_WHEN_READONLY = 'set-react-when-readonly'; @@ -133,7 +134,7 @@ export default class RoomInfoEditView extends LoggedView { let error = false; if (!this.formIsChanged()) { - showErrorAlert('Nothing to save!'); + showErrorAlert(I18n.t('Nothing_to_save')); return; } @@ -189,24 +190,24 @@ export default class RoomInfoEditView extends LoggedView { await this.setState({ saving: false }); setTimeout(() => { if (error) { - showErrorAlert('There was an error while saving settings!'); + showErrorAlert(I18n.t('There_was_an_error_while_saving_settings')); } else { - showToast('Settings succesfully changed!'); + showToast(I18n.t('Settings_succesfully_changed')); } }, 100); } delete = () => { Alert.alert( - 'Are you sure?', - 'Deleting a room will delete all messages posted within the room. This cannot be undone.', + I18n.t('Are_you_sure_question_mark'), + I18n.t('Delete_Room_Warning'), [ { - text: 'Cancel', + text: I18n.t('Cancel'), style: 'cancel' }, { - text: 'Yes, delete it!', + text: I18n.t('Yes_action_it', { action: I18n.t('delete') }), style: 'destructive', onPress: () => this.props.eraseRoom(this.state.room.rid) } @@ -217,17 +218,17 @@ export default class RoomInfoEditView extends LoggedView { toggleArchive = () => { const { archived } = this.state.room; - const action = `${ archived ? 'un' : '' }archive`; + const action = I18n.t(`${ archived ? 'un' : '' }archive`); Alert.alert( - 'Are you sure?', - `Do you really want to ${ action } this room?`, + I18n.t('Are_you_sure_question_mark'), + I18n.t('Do_you_really_want_to_key_this_room_question_mark', { key: action }), [ { - text: 'Cancel', + text: I18n.t('Cancel'), style: 'cancel' }, { - text: `Yes, ${ action } it!`, + text: I18n.t('Yes_action_it', { action }), style: 'destructive', onPress: () => { try { @@ -268,7 +269,7 @@ export default class RoomInfoEditView extends LoggedView { <View style={sharedStyles.formContainer}> <RCTextInput inputRef={(e) => { this.name = e; }} - label='Name' + label={I18n.t('Name')} value={name} onChangeText={value => this.setState({ name: value })} onSubmitEditing={() => { this.description.focus(); }} @@ -277,7 +278,7 @@ export default class RoomInfoEditView extends LoggedView { /> <RCTextInput inputRef={(e) => { this.description = e; }} - label='Description' + label={I18n.t('Description')} value={description} onChangeText={value => this.setState({ description: value })} onSubmitEditing={() => { this.topic.focus(); }} @@ -285,7 +286,7 @@ export default class RoomInfoEditView extends LoggedView { /> <RCTextInput inputRef={(e) => { this.topic = e; }} - label='Topic' + label={I18n.t('Topic')} value={topic} onChangeText={value => this.setState({ topic: value })} onSubmitEditing={() => { this.announcement.focus(); }} @@ -293,7 +294,7 @@ export default class RoomInfoEditView extends LoggedView { /> <RCTextInput inputRef={(e) => { this.announcement = e; }} - label='Announcement' + label={I18n.t('Announcement')} value={announcement} onChangeText={value => this.setState({ announcement: value })} onSubmitEditing={() => { this.joinCode.focus(); }} @@ -301,7 +302,7 @@ export default class RoomInfoEditView extends LoggedView { /> <RCTextInput inputRef={(e) => { this.joinCode = e; }} - label='Password' + label={I18n.t('Password')} value={joinCode} onChangeText={value => this.setState({ joinCode: value })} onSubmitEditing={this.submit} @@ -310,19 +311,19 @@ export default class RoomInfoEditView extends LoggedView { /> <SwitchContainer value={t} - leftLabelPrimary='Public' - leftLabelSecondary='Everyone can access this channel' - rightLabelPrimary='Private' - rightLabelSecondary='Just invited people can access this channel' + leftLabelPrimary={I18n.t('Public')} + leftLabelSecondary={I18n.t('Everyone_can_access_this_channel')} + rightLabelPrimary={I18n.t('Private')} + rightLabelSecondary={I18n.t('Just_invited_people_can_access_this_channel')} onValueChange={value => this.setState({ t: value })} testID='room-info-edit-view-t' /> <SwitchContainer value={ro} - leftLabelPrimary='Colaborative' - leftLabelSecondary='All users in the channel can write new messages' - rightLabelPrimary='Read Only' - rightLabelSecondary='Only authorized users can write new messages' + leftLabelPrimary={I18n.t('Colaborative')} + leftLabelSecondary={I18n.t('All_users_in_the_channel_can_write_new_messages')} + rightLabelPrimary={I18n.t('Read_Only')} + rightLabelSecondary={I18n.t('Only_authorized_users_can_write_new_messages')} onValueChange={value => this.setState({ ro: value })} disabled={!this.permissions[PERMISSION_SET_READONLY] || room.broadcast} testID='room-info-edit-view-ro' @@ -330,10 +331,10 @@ export default class RoomInfoEditView extends LoggedView { {ro && !room.broadcast && <SwitchContainer value={reactWhenReadOnly} - leftLabelPrimary='No Reactions' - leftLabelSecondary='Reactions are disabled' - rightLabelPrimary='Allow Reactions' - rightLabelSecondary='Reactions are enabled' + leftLabelPrimary={I18n.t('No_Reactions')} + leftLabelSecondary={I18n.t('Reactions_are_disabled')} + rightLabelPrimary={I18n.t('Allow_Reactions')} + rightLabelSecondary={I18n.t('Reactions_are_enabled')} onValueChange={value => this.setState({ reactWhenReadOnly: value })} disabled={!this.permissions[PERMISSION_SET_REACT_WHEN_READONLY]} testID='room-info-edit-view-react-when-ro' @@ -341,7 +342,7 @@ export default class RoomInfoEditView extends LoggedView { } {room.broadcast && [ - <Text style={styles.broadcast}>Broadcast channel</Text>, + <Text style={styles.broadcast}>{I18n.t('Broadcast_Channel')}</Text>, <View style={styles.divider} /> ] } @@ -351,7 +352,7 @@ export default class RoomInfoEditView extends LoggedView { disabled={!this.formIsChanged()} testID='room-info-edit-view-submit' > - <Text style={sharedStyles.button} accessibilityTraits='button'>SAVE</Text> + <Text style={sharedStyles.button} accessibilityTraits='button'>{I18n.t('SAVE')}</Text> </TouchableOpacity> <View style={{ flexDirection: 'row' }}> <TouchableOpacity @@ -359,7 +360,7 @@ export default class RoomInfoEditView extends LoggedView { onPress={this.reset} testID='room-info-edit-view-reset' > - <Text style={sharedStyles.button_inverted} accessibilityTraits='button'>RESET</Text> + <Text style={sharedStyles.button_inverted} accessibilityTraits='button'>{I18n.t('RESET')}</Text> </TouchableOpacity> <TouchableOpacity style={[ @@ -373,7 +374,7 @@ export default class RoomInfoEditView extends LoggedView { testID='room-info-edit-view-archive' > <Text style={[sharedStyles.button_inverted, styles.colorDanger]} accessibilityTraits='button'> - { room.archived ? 'UNARCHIVE' : 'ARCHIVE' } + { room.archived ? I18n.t('UNARCHIVE') : I18n.t('ARCHIVE') } </Text> </TouchableOpacity> </View> @@ -389,7 +390,7 @@ export default class RoomInfoEditView extends LoggedView { disabled={!this.hasDeletePermission()} testID='room-info-edit-view-delete' > - <Text style={[sharedStyles.button_inverted, styles.colorDanger]} accessibilityTraits='button'>DELETE</Text> + <Text style={[sharedStyles.button_inverted, styles.colorDanger]} accessibilityTraits='button'>{I18n.t('DELETE')}</Text> </TouchableOpacity> </View> <Loading visible={this.state.saving} /> diff --git a/app/views/RoomInfoView/index.js b/app/views/RoomInfoView/index.js index 369b4e3a1bbbfc11fc5e56b4ad8b2c1b4853aaac..68209a655ad3fb75523cbf7febbb643ede49ebf5 100644 --- a/app/views/RoomInfoView/index.js +++ b/app/views/RoomInfoView/index.js @@ -16,6 +16,7 @@ import Touch from '../../utils/touch'; import log from '../../utils/log'; import RoomTypeIcon from '../../containers/RoomTypeIcon'; +import I18n from '../../i18n'; const PERMISSION_EDIT_ROOM = 'edit-room'; @@ -56,7 +57,7 @@ export default class RoomInfoView extends LoggedView { onPress={() => navigation.navigate({ key: 'RoomInfoEdit', routeName: 'RoomInfoEdit', params: { rid: navigation.state.params.rid } })} underlayColor='#ffffff' activeOpacity={0.5} - accessibilityLabel='edit' + accessibilityLabel={I18n.t('edit')} accessibilityTraits='button' testID='room-info-view-edit-button' > @@ -132,14 +133,14 @@ export default class RoomInfoView extends LoggedView { const [room] = this.rooms; this.setState({ room }); } - // TODO: translate + renderItem = (key, room) => ( <View style={styles.item}> - <Text style={styles.itemLabel}>{camelize(key)}</Text> + <Text style={styles.itemLabel}>{I18n.t(camelize(key))}</Text> <Text style={[styles.itemContent, !room[key] && styles.itemContent__empty]} testID={`room-info-view-${ key }`} - >{ room[key] ? room[key] : `No ${ key } provided.` } + >{ room[key] ? room[key] : I18n.t(`No_${ key }_provided`) } </Text> </View> ); @@ -147,7 +148,7 @@ export default class RoomInfoView extends LoggedView { renderRoles = () => ( this.state.roles.length > 0 && <View style={styles.item}> - <Text style={styles.itemLabel}>Roles</Text> + <Text style={styles.itemLabel}>{I18n.t('Roles')}</Text> <View style={styles.rolesContainer}> {this.state.roles.map(role => ( <View style={styles.roleBadge} key={role}> @@ -168,7 +169,7 @@ export default class RoomInfoView extends LoggedView { // TODO: translate return ( <View style={styles.item}> - <Text style={styles.itemLabel}>Timezone</Text> + <Text style={styles.itemLabel}>{I18n.t('Timezone')}</Text> <Text style={styles.itemContent}>{moment().utcOffset(utcOffset).format(this.props.Message_TimeFormat)} (UTC { utcOffset })</Text> </View> ); @@ -189,11 +190,11 @@ export default class RoomInfoView extends LoggedView { renderBroadcast = () => ( <View style={styles.item}> - <Text style={styles.itemLabel}>Broadcast Channel</Text> + <Text style={styles.itemLabel}>{I18n.t('Broadcast_Channel')}</Text> <Text style={styles.itemContent} testID='room-info-view-broadcast' - >Only authorized users can write new messages, but the other users will be able to reply + >{I18n.t('Broadcast_channel_Description')} </Text> </View> ) diff --git a/app/views/RoomMembersView/index.js b/app/views/RoomMembersView/index.js index 8c6e9286e308da77cb41680d5e377df614716e9a..68fc2d0cf1a9387447a99ed601919e3313e91ef5 100644 --- a/app/views/RoomMembersView/index.js +++ b/app/views/RoomMembersView/index.js @@ -14,6 +14,7 @@ import { goRoom } from '../../containers/routes/NavigationService'; import database from '../../lib/realm'; import { showToast } from '../../utils/info'; import log from '../../utils/log'; +import I18n from '../../i18n'; @connect(state => ({ user: state.login.user, @@ -26,7 +27,7 @@ export default class MentionedMessagesView extends LoggedView { static navigationOptions = ({ navigation }) => { const params = navigation.state.params || {}; - const label = params.allUsers ? 'All' : 'Online'; + const label = params.allUsers ? I18n.t('All') : I18n.t('Online'); if (params.allUsers === undefined) { return; } @@ -123,14 +124,14 @@ export default class MentionedMessagesView extends LoggedView { if (!this.permissions['mute-user']) { return; } - this.actionSheetOptions = ['Cancel']; + this.actionSheetOptions = [I18n.t('Cancel')]; const { muted } = this.state.room; const userIsMuted = !!muted.find(m => m.value === user.username); user.muted = userIsMuted; if (userIsMuted) { - this.actionSheetOptions.push('Unmute'); + this.actionSheetOptions.push(I18n.t('Unmute')); } else { - this.actionSheetOptions.push('Mute'); + this.actionSheetOptions.push(I18n.t('Mute')); } this.setState({ userLongPressed: user }); Vibration.vibrate(50); @@ -141,7 +142,7 @@ export default class MentionedMessagesView extends LoggedView { const { rid, userLongPressed } = this.state; try { await RocketChat.toggleMuteUserInRoom(rid, userLongPressed.username, !userLongPressed.muted); - showToast(`User has been ${ userLongPressed.muted ? 'unmuted' : 'muted' }!`); + showToast(I18n.t('User_has_been_key', { key: userLongPressed.muted ? I18n.t('unmuted') : I18n.t('muted') })); } catch (e) { log('handleMute', e); } @@ -164,7 +165,7 @@ export default class MentionedMessagesView extends LoggedView { style={styles.searchBox} onChangeText={text => this.onSearchChangeText(text)} returnKeyType='search' - placeholder='Search' + placeholder={I18n.t('Search')} clearButtonMode='while-editing' blurOnSubmit autoCorrect={false} @@ -209,7 +210,7 @@ export default class MentionedMessagesView extends LoggedView { <ActionSheet key='room-members-actionsheet' ref={o => this.ActionSheet = o} - title='Actions' + title={I18n.t('Actions')} options={this.actionSheetOptions} cancelButtonIndex={this.CANCEL_INDEX} onPress={this.handleActionPress} diff --git a/app/views/RoomView/Header/index.js b/app/views/RoomView/Header/index.js index 1301a8571e5066a4bbb57362395c7f332c8ccc0d..275150aade39610ee00dc19d576a8b7cfaad8363 100644 --- a/app/views/RoomView/Header/index.js +++ b/app/views/RoomView/Header/index.js @@ -11,28 +11,28 @@ import Avatar from '../../../containers/Avatar'; import { STATUS_COLORS } from '../../../constants/colors'; import styles from './styles'; import { closeRoom } from '../../../actions/room'; - import log from '../../../utils/log'; import RoomTypeIcon from '../../../containers/RoomTypeIcon'; +import I18n from '../../../i18n'; const title = (offline, connecting, authenticating, logged) => { if (offline) { - return 'You are offline...'; + return `${ I18n.t('You_are_offline') }...`; } if (connecting) { - return 'Connecting...'; + return `${ I18n.t('Connecting') }...`; } if (authenticating) { - return 'Authenticating...'; + return `${ I18n.t('Authenticating') }...`; } if (logged) { return null; } - return 'Not logged...'; + return `${ I18n.t('Not_logged') }...`; }; @connect(state => ({ @@ -87,7 +87,7 @@ export default class RoomHeaderView extends React.PureComponent { getUserStatusLabel() { const status = this.getUserStatus(); - return status.charAt(0).toUpperCase() + status.slice(1); + return I18n.t(status.charAt(0).toUpperCase() + status.slice(1)); } updateState = () => { @@ -104,7 +104,7 @@ export default class RoomHeaderView extends React.PureComponent { requestAnimationFrame(() => this.props.close()); }} tintColor='#292E35' - title='Back' + title={I18n.t('Back')} titleStyle={{ display: 'none' }} />); @@ -124,7 +124,7 @@ export default class RoomHeaderView extends React.PureComponent { let t = ''; if (!title(offline, connecting, authenticating, logged) && loading) { - t = 'Loading messages...'; + t = I18n.t('Loading_messages_ellipsis'); } else if (this.isDirect()) { t = this.getUserStatusLabel(); } else { @@ -177,7 +177,7 @@ export default class RoomHeaderView extends React.PureComponent { log('toggleFavorite', e); } }} - accessibilityLabel='Star room' + accessibilityLabel={I18n.t('Star_room')} accessibilityTraits='button' testID='room-view-header-star' > @@ -191,7 +191,7 @@ export default class RoomHeaderView extends React.PureComponent { <TouchableOpacity style={styles.headerButton} onPress={() => this.props.navigation.navigate({ key: 'RoomActions', routeName: 'RoomActions', params: { rid: this.state.room.rid } })} - accessibilityLabel='Room actions' + accessibilityLabel={I18n.t('Room_actions')} accessibilityTraits='button' testID='room-view-header-actions' > diff --git a/app/views/RoomView/UnreadSeparator.js b/app/views/RoomView/UnreadSeparator.js index 7892b1664dcc776ae6c781e68429814a312d53e5..00b4b9958c0a0d0c06e5e1353a24a53f20e5509d 100644 --- a/app/views/RoomView/UnreadSeparator.js +++ b/app/views/RoomView/UnreadSeparator.js @@ -1,5 +1,6 @@ import React from 'react'; import { View, StyleSheet, Text, LayoutAnimation } from 'react-native'; +import I18n from '../../i18n'; const styles = StyleSheet.create({ firstUnread: { @@ -30,7 +31,7 @@ export default class UnreadSeparator extends React.PureComponent { return ( <View style={styles.firstUnread}> <View style={styles.firstUnreadLine} /> - <Text style={styles.firstUnreadBadge}>unread messages</Text> + <Text style={styles.firstUnreadBadge}>{I18n.t('unread_messages')}</Text> </View> ); } diff --git a/app/views/RoomView/banner.js b/app/views/RoomView/banner.js deleted file mode 100644 index bc5424ff0eec55c89b26a24b78a02e79eda8bed0..0000000000000000000000000000000000000000 --- a/app/views/RoomView/banner.js +++ /dev/null @@ -1,22 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import { Text, View } from 'react-native'; -import { connect } from 'react-redux'; -import styles from './styles'; - -@connect(state => ({ - loading: state.messages.isFetching -}), null) -export default class Banner extends React.PureComponent { - static propTypes = { - loading: PropTypes.bool - }; - - render() { - return (this.props.loading ? ( - <View style={styles.bannerContainer}> - <Text style={styles.bannerText}>Loading new messages...</Text> - </View> - ) : null); - } -} diff --git a/app/views/RoomView/index.js b/app/views/RoomView/index.js index 5a7d3c741d37c9aa92a923447de14f4b9563970b..928da4fb130fcf8964915053ca4dc800d92a6ebd 100644 --- a/app/views/RoomView/index.js +++ b/app/views/RoomView/index.js @@ -2,12 +2,12 @@ import React from 'react'; import PropTypes from 'prop-types'; import { Text, View, Button } from 'react-native'; import { connect } from 'react-redux'; -import { bindActionCreators } from 'redux'; +// import { bindActionCreators } from 'redux'; import equal from 'deep-equal'; import LoggedView from '../View'; import { List } from './ListView'; -import * as actions from '../../actions'; +// import * as actions from '../../actions'; import { openRoom, setLastOpen } from '../../actions/room'; import { editCancel, toggleReactionPicker, actionsShow } from '../../actions/messages'; import database from '../../lib/realm'; @@ -21,17 +21,18 @@ import RoomsHeader from './Header'; import ReactionPicker from './ReactionPicker'; import styles from './styles'; import log from '../../utils/log'; +import I18n from '../../i18n'; @connect( state => ({ - Site_Url: state.settings.Site_Url || state.server ? state.server.server : '', - Message_TimeFormat: state.settings.Message_TimeFormat, + // Site_Url: state.settings.Site_Url || state.server ? state.server.server : '', + // Message_TimeFormat: state.settings.Message_TimeFormat, loading: state.messages.isFetching, user: state.login.user, actionMessage: state.messages.actionMessage }), dispatch => ({ - actions: bindActionCreators(actions, dispatch), + // actions: bindActionCreators(actions, dispatch), openRoom: room => dispatch(openRoom(room)), editCancel: () => dispatch(editCancel()), setLastOpen: date => dispatch(setLastOpen(date)), @@ -48,8 +49,8 @@ export default class RoomView extends LoggedView { editCancel: PropTypes.func, rid: PropTypes.string, name: PropTypes.string, - Site_Url: PropTypes.string, - Message_TimeFormat: PropTypes.string, + // Site_Url: PropTypes.string, + // Message_TimeFormat: PropTypes.string, loading: PropTypes.bool, actionMessage: PropTypes.object, toggleReactionPicker: PropTypes.func.isRequired, @@ -191,7 +192,7 @@ export default class RoomView extends LoggedView { if (!this.state.joined) { return ( <View> - <Text>You are in preview mode.</Text> + <Text>{I18n.t('You_are_in_preview_mode')}</Text> <Button title='Join' onPress={this.joinRoom} /> </View> ); @@ -199,14 +200,14 @@ export default class RoomView extends LoggedView { if (this.state.room.archived || this.isReadOnly()) { return ( <View style={styles.readOnly}> - <Text>This room is read only</Text> + <Text>{I18n.t('This_room_is_read_only')}</Text> </View> ); } if (this.isBlocked()) { return ( <View style={styles.blockedOrBlocker}> - <Text>This room is blocked</Text> + <Text>{I18n.t('This_room_is_blocked')}</Text> </View> ); } @@ -215,9 +216,9 @@ export default class RoomView extends LoggedView { renderHeader = () => { if (this.state.end) { - return <Text style={styles.loadingMore}>Start of conversation</Text>; + return <Text style={styles.loadingMore}>{I18n.t('Start_of_conversation')}</Text>; } - return <Text style={styles.loadingMore}>Loading more messages...</Text>; + return <Text style={styles.loadingMore}>{I18n.t('Loading_messages_ellipsis')}</Text>; } render() { return ( diff --git a/app/views/RoomsListView/Header/index.js b/app/views/RoomsListView/Header/index.js index 1945407544085bbb059013c9e8e6c92ca57e1669..a225b7d704078bd68b264f238bf34c17991a7859 100644 --- a/app/views/RoomsListView/Header/index.js +++ b/app/views/RoomsListView/Header/index.js @@ -14,25 +14,26 @@ import { STATUS_COLORS } from '../../../constants/colors'; import { setSearch } from '../../../actions/rooms'; import styles from './styles'; import log from '../../../utils/log'; +import I18n from '../../../i18n'; const title = (offline, connecting, authenticating, logged) => { if (offline) { - return 'offline...'; + return `${ I18n.t('Offline') }...`; } if (connecting) { - return 'Connecting...'; + return `${ I18n.t('Connecting') }...`; } if (authenticating) { - return 'Authenticating...'; + return `${ I18n.t('Authenticating') }...`; } if (logged) { return null; } - return 'Not logged...'; + return `${ I18n.t('Not_logged') }...`; }; @connect(state => ({ @@ -96,7 +97,7 @@ export default class RoomsListHeaderView extends React.PureComponent { getUserStatusLabel() { const status = this.getUserStatus(); - return status.charAt(0).toUpperCase() + status.slice(1); + return I18n.t(status.charAt(0).toUpperCase() + status.slice(1)); } showModal() { @@ -124,7 +125,7 @@ export default class RoomsListHeaderView extends React.PureComponent { <View style={styles.left} accessible - accessibilityLabel={`Connected to ${ this.props.baseUrl }. Tap to view servers list.`} + accessibilityLabel={`${ I18n.t('Connected_to') } ${ this.props.baseUrl }. ${ I18n.t('Tap_to_view_servers_list') }.`} accessibilityTraits='button' testID='rooms-list-view-sidebar' > @@ -156,7 +157,7 @@ export default class RoomsListHeaderView extends React.PureComponent { const t = title(offline, connecting, authenticating, logged); - const accessibilityLabel = `${ user.username }, ${ this.getUserStatusLabel() }, tap to change status`; + const accessibilityLabel = `${ user.username }, ${ this.getUserStatusLabel() }, ${ I18n.t('tap_to_change_status') }`; return ( <TouchableOpacity style={styles.titleContainer} @@ -190,7 +191,7 @@ export default class RoomsListHeaderView extends React.PureComponent { <TouchableOpacity style={styles.headerButton} onPress={() => this.onPressSearchButton()} - accessibilityLabel='Search' + accessibilityLabel={I18n.t('Search')} accessibilityTraits='button' > <Icon @@ -204,7 +205,7 @@ export default class RoomsListHeaderView extends React.PureComponent { <TouchableOpacity style={styles.headerButton} onPress={() => this.createChannel()} - accessibilityLabel='Create channel' + accessibilityLabel={I18n.t('Create_Channel')} accessibilityTraits='button' testID='rooms-list-view-create-channel' > @@ -223,6 +224,7 @@ export default class RoomsListHeaderView extends React.PureComponent { renderModalButton = (status, text) => { const statusStyle = [styles.status, { marginRight: 10, backgroundColor: STATUS_COLORS[status] }]; const textStyle = { flex: 1, fontWeight: this.props.user.status === status ? 'bold' : 'normal' }; + const label = text || status; return ( <TouchableOpacity style={styles.modalButton} @@ -231,7 +233,7 @@ export default class RoomsListHeaderView extends React.PureComponent { > <View style={statusStyle} /> <Text style={textStyle}> - {text || status.charAt(0).toUpperCase() + status.slice(1)} + {I18n.t(label.charAt(0).toUpperCase() + label.slice(1))} </Text> </TouchableOpacity> ); @@ -252,7 +254,7 @@ export default class RoomsListHeaderView extends React.PureComponent { style={styles.inputSearch} onChangeText={text => this.onSearchChangeText(text)} returnKeyType='search' - placeholder='Search' + placeholder={I18n.t('Search')} clearButtonMode='while-editing' blurOnSubmit autoCorrect={false} @@ -281,7 +283,7 @@ export default class RoomsListHeaderView extends React.PureComponent { {this.renderModalButton('online')} {this.renderModalButton('busy')} {this.renderModalButton('away')} - {this.renderModalButton('offline', 'Invisible')} + {this.renderModalButton('offline', 'invisible')} </View> </Modal> </View> diff --git a/app/views/RoomsListView/index.js b/app/views/RoomsListView/index.js index d1c8726fc0ad57ba1301a7115e43cd46e862af97..929e0cfb9769c372f0425e79116e1e2e43590ebe 100644 --- a/app/views/RoomsListView/index.js +++ b/app/views/RoomsListView/index.js @@ -14,6 +14,7 @@ import styles from './styles'; import debounce from '../../utils/debounce'; import LoggedView from '../View'; import log from '../../utils/log'; +import I18n from '../../i18n'; @connect(state => ({ user: state.login.user, @@ -166,7 +167,7 @@ export default class RoomsListView extends LoggedView { style={styles.searchBox} onChangeText={text => this.onSearchChangeText(text)} returnKeyType='search' - placeholder='Search' + placeholder={I18n.t('Search')} clearButtonMode='while-editing' blurOnSubmit autoCorrect={false} @@ -213,7 +214,7 @@ export default class RoomsListView extends LoggedView { renderCreateButtons = () => ( <ActionButton buttonColor='rgba(231,76,60,1)'> - <ActionButton.Item buttonColor='#9b59b6' title='Create Channel' onPress={() => { this.createChannel(); }} > + <ActionButton.Item buttonColor='#9b59b6' title={I18n.t('Create_Channel')} onPress={() => { this.createChannel(); }} > <Icon name='md-chatbubbles' style={styles.actionButtonIcon} /> </ActionButton.Item> </ActionButton> diff --git a/app/views/SearchMessagesView/index.js b/app/views/SearchMessagesView/index.js index f7ca2176702ebff50cb38b42c309f6da2698d8a5..4aa45da269a70f8c1482c3eef9a0b884f56d149e 100644 --- a/app/views/SearchMessagesView/index.js +++ b/app/views/SearchMessagesView/index.js @@ -14,6 +14,7 @@ import buildMessage from '../../lib/methods/helpers/buildMessage'; import Message from '../../containers/message'; import scrollPersistTaps from '../../utils/scrollPersistTaps'; import log from '../../utils/log'; +import I18n from '../../i18n'; @connect(state => ({ user: state.login.user, @@ -114,12 +115,12 @@ export default class SearchMessagesView extends LoggedView { <View style={styles.searchContainer}> <RCTextInput inputRef={(e) => { this.name = e; }} - label='Search' + label={I18n.t('Search')} onChangeText={this.onChangeSearch} - placeholder='Search Messages' + placeholder={I18n.t('Search_Messages')} testID='search-message-view-input' /> - <Markdown msg='You can search using RegExp. e.g. `/^text$/i`' /> + <Markdown msg={I18n.t('You_can_search_using_RegExp_eg')} /> <View style={styles.divider} /> </View> <FlatList diff --git a/app/views/SelectedUsersView.js b/app/views/SelectedUsersView.js index 712fe67e06c86aa743eb81940264ad8a69d5f7c5..d1582ccc80bf7306f365c047d6dd40651fa663c2 100644 --- a/app/views/SelectedUsersView.js +++ b/app/views/SelectedUsersView.js @@ -12,6 +12,7 @@ import Avatar from '../containers/Avatar'; import Loading from '../containers/Loading'; import debounce from '../utils/debounce'; import LoggedView from './View'; +import I18n from '../i18n'; const styles = StyleSheet.create({ container: { @@ -101,7 +102,7 @@ export default class SelectedUsersView extends LoggedView { justifyContent: 'center' }} onPress={() => params.nextAction()} - accessibilityLabel='Submit' + accessibilityLabel={I18n.t('Submit')} accessibilityTraits='button' testID='selected-users-view-submit' > @@ -227,7 +228,7 @@ export default class SelectedUsersView extends LoggedView { style={styles.searchBox} onChangeText={text => this.onSearchChangeText(text)} returnKeyType='search' - placeholder='Search' + placeholder={I18n.t('Search')} clearButtonMode='while-editing' blurOnSubmit testID='select-users-view-search' diff --git a/app/views/SnippetedMessagesView/index.js b/app/views/SnippetedMessagesView/index.js index 9535e28c2ab8233ebcf92343a6a6265b74b2a782..f1b2400235493a252769f05ee9e8904c4218dcea 100644 --- a/app/views/SnippetedMessagesView/index.js +++ b/app/views/SnippetedMessagesView/index.js @@ -8,6 +8,7 @@ import { openSnippetedMessages, closeSnippetedMessages } from '../../actions/sni import styles from './styles'; import Message from '../../containers/message'; import RCActivityIndicator from '../../containers/ActivityIndicator'; +import I18n from '../../i18n'; @connect( state => ({ @@ -74,7 +75,7 @@ export default class SnippetedMessagesView extends LoggedView { renderEmpty = () => ( <View style={styles.listEmptyContainer} testID='snippeted-messages-view'> - <Text>No snippeted messages</Text> + <Text>{I18n.t('No_snippeted_messages')}</Text> </View> ) diff --git a/app/views/StarredMessagesView/index.js b/app/views/StarredMessagesView/index.js index f9df1e04da5123de0372749bd96d9a1345730b8b..6feed3c3bbf9ec86a0147922bfb1377871d6215c 100644 --- a/app/views/StarredMessagesView/index.js +++ b/app/views/StarredMessagesView/index.js @@ -10,10 +10,11 @@ import styles from './styles'; import Message from '../../containers/message'; import { toggleStarRequest } from '../../actions/messages'; import RCActivityIndicator from '../../containers/ActivityIndicator'; +import I18n from '../../i18n'; const STAR_INDEX = 0; const CANCEL_INDEX = 1; -const options = ['Unstar', 'Cancel']; +const options = [I18n.t('Unstar'), I18n.t('Cancel')]; @connect( state => ({ @@ -98,7 +99,7 @@ export default class StarredMessagesView extends LoggedView { renderEmpty = () => ( <View style={styles.listEmptyContainer} testID='starred-messages-view'> - <Text>No starred messages</Text> + <Text>{I18n.t('No_starred_messages')}</Text> </View> ) @@ -138,7 +139,7 @@ export default class StarredMessagesView extends LoggedView { <ActionSheet key='starred-messages-view-action-sheet' ref={o => this.actionSheet = o} - title='Actions' + title={I18n.t('Actions')} options={options} cancelButtonIndex={CANCEL_INDEX} onPress={this.handleActionPress} diff --git a/app/views/TermsServiceView.js b/app/views/TermsServiceView.js index b56abca08a0b2b711af7fe127a0d1bd6d653750c..f27d30a41ace324610a5f1388863a63a925be483 100644 --- a/app/views/TermsServiceView.js +++ b/app/views/TermsServiceView.js @@ -3,15 +3,11 @@ import PropTypes from 'prop-types'; import { WebView } from 'react-native'; import { connect } from 'react-redux'; -class TermsServiceView extends React.Component { +class TermsServiceView extends React.PureComponent { static propTypes = { termsService: PropTypes.string } - static navigationOptions = () => ({ - title: 'Terms of service' - }); - render() { return ( <WebView source={{ html: this.props.termsService }} /> diff --git a/e2e/07-room.spec.js b/e2e/07-room.spec.js index e3cb3874d7abe9023626c86866233ef54348b414..70490aef9ee98d4ee02318ccebdb5a2c2423c8b1 100644 --- a/e2e/07-room.spec.js +++ b/e2e/07-room.spec.js @@ -178,16 +178,16 @@ describe('Room screen', () => { describe('Message', async() => { it('should show message actions', async() => { await element(by.text(`${ data.random }message`)).longPress(); - await waitFor(element(by.text('Messages actions'))).toBeVisible().withTimeout(5000); - await expect(element(by.text('Messages actions'))).toBeVisible(); + await waitFor(element(by.text('Message actions'))).toBeVisible().withTimeout(5000); + await expect(element(by.text('Message actions'))).toBeVisible(); await element(by.text('Cancel')).tap(); await waitFor(element(by.text('Cancel'))).toBeNotVisible().withTimeout(2000); }); it('should copy permalink', async() => { await element(by.text(`${ data.random }message`)).longPress(); - await waitFor(element(by.text('Messages actions'))).toBeVisible().withTimeout(5000); - await expect(element(by.text('Messages actions'))).toBeVisible(); + await waitFor(element(by.text('Message actions'))).toBeVisible().withTimeout(5000); + await expect(element(by.text('Message actions'))).toBeVisible(); await element(by.text('Copy Permalink')).tap(); await expect(element(by.text('Permalink copied to clipboard!'))).toBeVisible(); await waitFor(element(by.text('Permalink copied to clipboard!'))).toBeNotVisible().withTimeout(5000); @@ -197,8 +197,8 @@ describe('Room screen', () => { it('should copy message', async() => { await element(by.text(`${ data.random }message`)).longPress(); - await waitFor(element(by.text('Messages actions'))).toBeVisible().withTimeout(5000); - await expect(element(by.text('Messages actions'))).toBeVisible(); + await waitFor(element(by.text('Message actions'))).toBeVisible().withTimeout(5000); + await expect(element(by.text('Message actions'))).toBeVisible(); await element(by.text('Copy Message')).tap(); await expect(element(by.text('Copied to clipboard!'))).toBeVisible(); await waitFor(element(by.text('Copied to clipboard!'))).toBeNotVisible().withTimeout(5000); @@ -207,10 +207,10 @@ describe('Room screen', () => { it('should star message', async() => { await element(by.text(`${ data.random }message`)).longPress(); - await waitFor(element(by.text('Messages actions'))).toBeVisible().withTimeout(5000); - await expect(element(by.text('Messages actions'))).toBeVisible(); + await waitFor(element(by.text('Message actions'))).toBeVisible().withTimeout(5000); + await expect(element(by.text('Message actions'))).toBeVisible(); await element(by.text('Star')).tap(); - await waitFor(element(by.text('Messages actions'))).toBeNotVisible().withTimeout(5000); + await waitFor(element(by.text('Message actions'))).toBeNotVisible().withTimeout(5000); await element(by.text(`${ data.random }message`)).longPress(); await waitFor(element(by.text('Unstar'))).toBeVisible().withTimeout(2000); await expect(element(by.text('Unstar'))).toBeVisible(); @@ -220,8 +220,8 @@ describe('Room screen', () => { it('should react to message', async() => { await element(by.text(`${ data.random }message`)).longPress(); - await waitFor(element(by.text('Messages actions'))).toBeVisible().withTimeout(5000); - await expect(element(by.text('Messages actions'))).toBeVisible(); + await waitFor(element(by.text('Message actions'))).toBeVisible().withTimeout(5000); + await expect(element(by.text('Message actions'))).toBeVisible(); await element(by.text('Add Reaction')).tap(); await waitFor(element(by.id('reaction-picker'))).toBeVisible().withTimeout(2000); await expect(element(by.id('reaction-picker'))).toBeVisible(); @@ -254,8 +254,8 @@ describe('Room screen', () => { it('should reply message', async() => { await mockMessage('reply'); await element(by.text(`${ data.random }reply`)).longPress(); - await waitFor(element(by.text('Messages actions'))).toBeVisible().withTimeout(5000); - await expect(element(by.text('Messages actions'))).toBeVisible(); + await waitFor(element(by.text('Message actions'))).toBeVisible().withTimeout(5000); + await expect(element(by.text('Message actions'))).toBeVisible(); await element(by.text('Reply')).tap(); await element(by.id('messagebox-input')).typeText('replied'); await element(by.id('messagebox-send-message')).tap(); @@ -265,8 +265,8 @@ describe('Room screen', () => { it('should edit message', async() => { await mockMessage('edit'); await element(by.text(`${ data.random }edit`)).longPress(); - await waitFor(element(by.text('Messages actions'))).toBeVisible().withTimeout(5000); - await expect(element(by.text('Messages actions'))).toBeVisible(); + await waitFor(element(by.text('Message actions'))).toBeVisible().withTimeout(5000); + await expect(element(by.text('Message actions'))).toBeVisible(); await element(by.text('Edit')).tap(); await element(by.id('messagebox-input')).typeText('ed'); await element(by.id('messagebox-send-message')).tap(); @@ -277,8 +277,8 @@ describe('Room screen', () => { it('should quote message', async() => { await mockMessage('quote'); await element(by.text(`${ data.random }quote`)).longPress(); - await waitFor(element(by.text('Messages actions'))).toBeVisible().withTimeout(5000); - await expect(element(by.text('Messages actions'))).toBeVisible(); + await waitFor(element(by.text('Message actions'))).toBeVisible().withTimeout(5000); + await expect(element(by.text('Message actions'))).toBeVisible(); await element(by.text('Quote')).tap(); await element(by.id('messagebox-input')).typeText(`${ data.random }quoted`); await element(by.id('messagebox-send-message')).tap(); @@ -287,10 +287,10 @@ describe('Room screen', () => { it('should pin message', async() => { await element(by.text(`${ data.random }edited`)).longPress(); - await waitFor(element(by.text('Messages actions'))).toBeVisible().withTimeout(5000); - await expect(element(by.text('Messages actions'))).toBeVisible(); + await waitFor(element(by.text('Message actions'))).toBeVisible().withTimeout(5000); + await expect(element(by.text('Message actions'))).toBeVisible(); await element(by.text('Pin')).tap(); - await waitFor(element(by.text('Messages actions'))).toBeNotVisible().withTimeout(5000); + await waitFor(element(by.text('Message actions'))).toBeNotVisible().withTimeout(5000); await waitFor(element(by.text(`${ data.random }edited`)).atIndex(1)).toBeVisible().withTimeout(60000); await element(by.text(`${ data.random }edited`)).atIndex(0).longPress(); await waitFor(element(by.text('Unpin'))).toBeVisible().withTimeout(2000); diff --git a/ios/RocketChatRN.xcodeproj/project.pbxproj b/ios/RocketChatRN.xcodeproj/project.pbxproj index 800e7e1a18b010252f5db2e04f458bf4c215ec71..9ce7902886d54fd04f661c90b4eb85ab10c8ff0c 100644 --- a/ios/RocketChatRN.xcodeproj/project.pbxproj +++ b/ios/RocketChatRN.xcodeproj/project.pbxproj @@ -5,6 +5,7 @@ }; objectVersion = 46; objects = { + /* Begin PBXBuildFile section */ 00C302E51ABCBA2D00DB3ED1 /* libRCTActionSheet.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 00C302AC1ABCB8CE00DB3ED1 /* libRCTActionSheet.a */; }; 00C302E71ABCBA2D00DB3ED1 /* libRCTGeolocation.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 00C302BA1ABCB90400DB3ED1 /* libRCTGeolocation.a */; }; @@ -12,6 +13,7 @@ 00C302E91ABCBA2D00DB3ED1 /* libRCTNetwork.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 00C302DC1ABCB9D200DB3ED1 /* libRCTNetwork.a */; }; 00C302EA1ABCBA2D00DB3ED1 /* libRCTVibration.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 00C302E41ABCB9EE00DB3ED1 /* libRCTVibration.a */; }; 00E356F31AD99517003FC87E /* RocketChatRNTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 00E356F21AD99517003FC87E /* RocketChatRNTests.m */; }; + 09CB5909C1E64707832358CE /* libRNI18n-tvOS.a in Frameworks */ = {isa = PBXBuildFile; fileRef = C01CD6D4653143EEB5100C3A /* libRNI18n-tvOS.a */; }; 0C6E2DE448364EA896869ADF /* libc++.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = B37C79D9BD0742CE936B6982 /* libc++.tbd */; }; 0DC38A29B0E54AF4AF96CB95 /* MaterialCommunityIcons.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 2EADB1731B5E47D093292B59 /* MaterialCommunityIcons.ttf */; }; 0F026E58B8A6427D9A204D89 /* libSplashScreen.a in Frameworks */ = {isa = PBXBuildFile; fileRef = B2607FA180F14E6584301101 /* libSplashScreen.a */; }; @@ -65,12 +67,13 @@ B8C682AD1FD8511E003A12C8 /* Ionicons.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 1B0746E708284151B8AD1198 /* Ionicons.ttf */; }; B8C682AE1FD8511F003A12C8 /* Ionicons.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 1B0746E708284151B8AD1198 /* Ionicons.ttf */; }; B8E79AF41F3CD167005B464F /* Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB61A68108700A75B9A /* Info.plist */; }; + BAB7DC22804246F3923A1833 /* libFastImage.a in Frameworks */ = {isa = PBXBuildFile; fileRef = FD2E2837F110483CA29EE0D4 /* libFastImage.a */; }; BED2B77AA660460E8BC9F8E0 /* libRNFetchBlob.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 6533FB90166345D29F1B91C0 /* libRNFetchBlob.a */; }; C758F0BD5C3244E2BA073E61 /* libRNImagePicker.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 3B696712EE2345A59F007A88 /* libRNImagePicker.a */; }; CBD0E0A35B174C4DBFED3B31 /* Zocial.ttf in Resources */ = {isa = PBXBuildFile; fileRef = E528DE3A405E43B4A37ABA68 /* Zocial.ttf */; }; D6408D9E4A864FF6BA986857 /* SimpleLineIcons.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 8A2DD67ADD954AD9873F45FC /* SimpleLineIcons.ttf */; }; EF736EF520A64AE8820E684A /* libRealmReact.a in Frameworks */ = {isa = PBXBuildFile; fileRef = DF26CC845883492D8AC8869B /* libRealmReact.a */; }; - BAB7DC22804246F3923A1833 /* libFastImage.a in Frameworks */ = {isa = PBXBuildFile; fileRef = FD2E2837F110483CA29EE0D4 /* libFastImage.a */; }; + F5BF54DC78E1411B8343933B /* libRNI18n.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 921481B47B50490CA761932E /* libRNI18n.a */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -326,6 +329,27 @@ remoteGlobalIDString = 39DF4FE71E00394E00F5B4B2; remoteInfo = RCTCustomInputController; }; + 7A770EC120BECDC7001AD51A /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 1845C223DA364898A8400573 /* FastImage.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = A287971D1DE0C0A60081BDFA; + remoteInfo = FastImage; + }; + 7A770EC520BECDC7001AD51A /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 22D3971EAF2E4660B4FAB3DD /* RNI18n.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 134814201AA4EA6300B7C361; + remoteInfo = RNI18n; + }; + 7A770EC720BECDC7001AD51A /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 22D3971EAF2E4660B4FAB3DD /* RNI18n.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 6476C4051EEAA69700B10F51; + remoteInfo = "RNI18n-tvOS"; + }; 7A7F5C981FCC982500024129 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = AD0379F2BCE84C968538CDAF /* RCTVideo.xcodeproj */; @@ -490,10 +514,12 @@ 13B07FB61A68108700A75B9A /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Info.plist; path = RocketChatRN/Info.plist; sourceTree = "<group>"; }; 13B07FB71A68108700A75B9A /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = main.m; path = RocketChatRN/main.m; sourceTree = "<group>"; }; 146833FF1AC3E56700842450 /* React.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = React.xcodeproj; path = "../node_modules/react-native/React/React.xcodeproj"; sourceTree = "<group>"; }; + 1845C223DA364898A8400573 /* FastImage.xcodeproj */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = "wrapper.pb-project"; name = FastImage.xcodeproj; path = "../node_modules/react-native-fast-image/ios/FastImage.xcodeproj"; sourceTree = "<group>"; }; 1B0746E708284151B8AD1198 /* Ionicons.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = file; name = Ionicons.ttf; path = "../node_modules/react-native-vector-icons/Fonts/Ionicons.ttf"; sourceTree = "<group>"; }; 1D3BB00B9ABF44EA9BD71318 /* libSafariViewManager.a */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = archive.ar; path = libSafariViewManager.a; sourceTree = "<group>"; }; 20CE3E407E0D4D9E8C9885F2 /* libRCTVideo.a */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = archive.ar; path = libRCTVideo.a; sourceTree = "<group>"; }; 22A8B76C8EBA443BB97CE82D /* RNVectorIcons.xcodeproj */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = "wrapper.pb-project"; name = RNVectorIcons.xcodeproj; path = "../node_modules/react-native-vector-icons/RNVectorIcons.xcodeproj"; sourceTree = "<group>"; }; + 22D3971EAF2E4660B4FAB3DD /* RNI18n.xcodeproj */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = "wrapper.pb-project"; name = RNI18n.xcodeproj; path = "../node_modules/react-native-i18n/ios/RNI18n.xcodeproj"; sourceTree = "<group>"; }; 2D02E47B1E0B4A5D006451C7 /* RocketChatRN-tvOS.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "RocketChatRN-tvOS.app"; sourceTree = BUILT_PRODUCTS_DIR; }; 2D02E4901E0B4A5D006451C7 /* RocketChatRN-tvOSTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "RocketChatRN-tvOSTests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; 2EADB1731B5E47D093292B59 /* MaterialCommunityIcons.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = MaterialCommunityIcons.ttf; path = "../node_modules/react-native-vector-icons/Fonts/MaterialCommunityIcons.ttf"; sourceTree = "<group>"; }; @@ -519,6 +545,7 @@ 7AFB8035205AE63000D004E7 /* RCTToast.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTToast.xcodeproj; path = "../node_modules/@remobile/react-native-toast/ios/RCTToast.xcodeproj"; sourceTree = "<group>"; }; 832341B01AAA6A8300B99B32 /* RCTText.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTText.xcodeproj; path = "../node_modules/react-native/Libraries/Text/RCTText.xcodeproj"; sourceTree = "<group>"; }; 8A2DD67ADD954AD9873F45FC /* SimpleLineIcons.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = SimpleLineIcons.ttf; path = "../node_modules/react-native-vector-icons/Fonts/SimpleLineIcons.ttf"; sourceTree = "<group>"; }; + 921481B47B50490CA761932E /* libRNI18n.a */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = archive.ar; path = libRNI18n.a; sourceTree = "<group>"; }; 9A1E1766CCB84C91A62BD5A6 /* Foundation.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = Foundation.ttf; path = "../node_modules/react-native-vector-icons/Fonts/Foundation.ttf"; sourceTree = "<group>"; }; A18EFC3B0CFE40E0918A8F0C /* EvilIcons.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = EvilIcons.ttf; path = "../node_modules/react-native-vector-icons/Fonts/EvilIcons.ttf"; sourceTree = "<group>"; }; AD0379F2BCE84C968538CDAF /* RCTVideo.xcodeproj */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = "wrapper.pb-project"; name = RCTVideo.xcodeproj; path = "../node_modules/react-native-video/ios/RCTVideo.xcodeproj"; sourceTree = "<group>"; }; @@ -528,6 +555,7 @@ B8971BAC202A091D0000D245 /* KeyboardTrackingView.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = KeyboardTrackingView.xcodeproj; path = "../node_modules/react-native-keyboard-tracking-view/lib/KeyboardTrackingView.xcodeproj"; sourceTree = "<group>"; }; B8C682611FD84CEF003A12C8 /* icomoon.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; name = icomoon.ttf; path = ../resources/fonts/icomoon.ttf; sourceTree = "<group>"; }; BAAE4B947F5D44959F0A9D5A /* libRNZeroconf.a */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = archive.ar; path = libRNZeroconf.a; sourceTree = "<group>"; }; + C01CD6D4653143EEB5100C3A /* libRNI18n-tvOS.a */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = archive.ar; path = "libRNI18n-tvOS.a"; sourceTree = "<group>"; }; C21010507E5B4B37BA0E4C9D /* RNAudio.xcodeproj */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = "wrapper.pb-project"; name = RNAudio.xcodeproj; path = "../node_modules/react-native-audio/ios/RNAudio.xcodeproj"; sourceTree = "<group>"; }; C23AEF1D9EBE4A38A1A6B97B /* RNSVG.xcodeproj */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = "wrapper.pb-project"; name = RNSVG.xcodeproj; path = "../node_modules/react-native-svg/ios/RNSVG.xcodeproj"; sourceTree = "<group>"; }; DA50CE47374C4C35BE6D9D58 /* libRNSVG.a */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = archive.ar; path = libRNSVG.a; sourceTree = "<group>"; }; @@ -535,8 +563,7 @@ DF26CC845883492D8AC8869B /* libRealmReact.a */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = archive.ar; path = libRealmReact.a; sourceTree = "<group>"; }; E528DE3A405E43B4A37ABA68 /* Zocial.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = Zocial.ttf; path = "../node_modules/react-native-vector-icons/Fonts/Zocial.ttf"; sourceTree = "<group>"; }; F88C6541BD764BEEABB87272 /* Octicons.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = Octicons.ttf; path = "../node_modules/react-native-vector-icons/Fonts/Octicons.ttf"; sourceTree = "<group>"; }; - 1845C223DA364898A8400573 /* FastImage.xcodeproj */ = {isa = PBXFileReference; name = "FastImage.xcodeproj"; path = "../node_modules/react-native-fast-image/ios/FastImage.xcodeproj"; sourceTree = "<group>"; fileEncoding = undefined; lastKnownFileType = wrapper.pb-project; explicitFileType = undefined; includeInIndex = 0; }; - FD2E2837F110483CA29EE0D4 /* libFastImage.a */ = {isa = PBXFileReference; name = "libFastImage.a"; path = "libFastImage.a"; sourceTree = "<group>"; fileEncoding = undefined; lastKnownFileType = archive.ar; explicitFileType = undefined; includeInIndex = 0; }; + FD2E2837F110483CA29EE0D4 /* libFastImage.a */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = archive.ar; path = libFastImage.a; sourceTree = "<group>"; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -583,6 +610,7 @@ 2C800DF680F8451599E80AF1 /* libSafariViewManager.a in Frameworks */, 74815BBCB91147C08C8F7B3D /* libRNAudio.a in Frameworks */, BAB7DC22804246F3923A1833 /* libFastImage.a in Frameworks */, + F5BF54DC78E1411B8343933B /* libRNI18n.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -598,6 +626,7 @@ 2D02E4C61E0B4AEC006451C7 /* libRCTSettings-tvOS.a in Frameworks */, 2D02E4C71E0B4AEC006451C7 /* libRCTText-tvOS.a in Frameworks */, 2D02E4C81E0B4AEC006451C7 /* libRCTWebSocket-tvOS.a in Frameworks */, + 09CB5909C1E64707832358CE /* libRNI18n-tvOS.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -788,6 +817,23 @@ name = Products; sourceTree = "<group>"; }; + 7A770EBC20BECDC7001AD51A /* Products */ = { + isa = PBXGroup; + children = ( + 7A770EC620BECDC7001AD51A /* libRNI18n.a */, + 7A770EC820BECDC7001AD51A /* libRNI18n-tvOS.a */, + ); + name = Products; + sourceTree = "<group>"; + }; + 7A770EBE20BECDC7001AD51A /* Products */ = { + isa = PBXGroup; + children = ( + 7A770EC220BECDC7001AD51A /* libFastImage.a */, + ); + name = Products; + sourceTree = "<group>"; + }; 7A7F5C831FCC982500024129 /* Products */ = { isa = PBXGroup; children = ( @@ -843,6 +889,7 @@ 4019A5E1911B4C61944FBCEC /* SafariViewManager.xcodeproj */, C21010507E5B4B37BA0E4C9D /* RNAudio.xcodeproj */, 1845C223DA364898A8400573 /* FastImage.xcodeproj */, + 22D3971EAF2E4660B4FAB3DD /* RNI18n.xcodeproj */, ); name = Libraries; sourceTree = "<group>"; @@ -948,6 +995,9 @@ B2607FA180F14E6584301101 /* libSplashScreen.a */, 1D3BB00B9ABF44EA9BD71318 /* libSafariViewManager.a */, 1142E3442BA94B19BCF52814 /* libRNAudio.a */, + FD2E2837F110483CA29EE0D4 /* libFastImage.a */, + 921481B47B50490CA761932E /* libRNI18n.a */, + C01CD6D4653143EEB5100C3A /* libRNI18n-tvOS.a */, ); name = "Recovered References"; sourceTree = "<group>"; @@ -1109,6 +1159,10 @@ productRefGroup = 83CBBA001A601CBA00E9B192 /* Products */; projectDirPath = ""; projectReferences = ( + { + ProductGroup = 7A770EBE20BECDC7001AD51A /* Products */; + ProjectRef = 1845C223DA364898A8400573 /* FastImage.xcodeproj */; + }, { ProductGroup = B8971BAD202A091D0000D245 /* Products */; ProjectRef = B8971BAC202A091D0000D245 /* KeyboardTrackingView.xcodeproj */; @@ -1185,6 +1239,10 @@ ProductGroup = B8E79A881F3CCC6C005B464F /* Products */; ProjectRef = 4CD38E4891ED4601B7481448 /* RNFetchBlob.xcodeproj */; }, + { + ProductGroup = 7A770EBC20BECDC7001AD51A /* Products */; + ProjectRef = 22D3971EAF2E4660B4FAB3DD /* RNI18n.xcodeproj */; + }, { ProductGroup = 60B8375C1F3F6F4B00677E56 /* Products */; ProjectRef = 4B38C7E37A8748E0BC665078 /* RNImagePicker.xcodeproj */; @@ -1463,6 +1521,27 @@ remoteRef = 7A430E1D20238C02008F55BC /* PBXContainerItemProxy */; sourceTree = BUILT_PRODUCTS_DIR; }; + 7A770EC220BECDC7001AD51A /* libFastImage.a */ = { + isa = PBXReferenceProxy; + fileType = archive.ar; + path = libFastImage.a; + remoteRef = 7A770EC120BECDC7001AD51A /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 7A770EC620BECDC7001AD51A /* libRNI18n.a */ = { + isa = PBXReferenceProxy; + fileType = archive.ar; + path = libRNI18n.a; + remoteRef = 7A770EC520BECDC7001AD51A /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + 7A770EC820BECDC7001AD51A /* libRNI18n-tvOS.a */ = { + isa = PBXReferenceProxy; + fileType = archive.ar; + path = "libRNI18n-tvOS.a"; + remoteRef = 7A770EC720BECDC7001AD51A /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; 7A7F5C991FCC982500024129 /* libRCTVideo.a */ = { isa = PBXReferenceProxy; fileType = archive.ar; @@ -1773,6 +1852,7 @@ "$(SRCROOT)/../node_modules/react-native-safari-view", "$(SRCROOT)/../node_modules/react-native-audio/ios", "$(SRCROOT)/../node_modules/react-native-fast-image/ios/FastImage/**", + "$(SRCROOT)/../node_modules/react-native-i18n/ios", ); INFOPLIST_FILE = RocketChatRNTests/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 8.0; @@ -1787,6 +1867,8 @@ "\"$(SRCROOT)/$(TARGET_NAME)\"", "\"$(SRCROOT)/$(TARGET_NAME)\"", "\"$(SRCROOT)/$(TARGET_NAME)\"", + "\"$(SRCROOT)/$(TARGET_NAME)\"", + "\"$(SRCROOT)/$(TARGET_NAME)\"", ); OTHER_LDFLAGS = ( "-ObjC", @@ -1818,6 +1900,7 @@ "$(SRCROOT)/../node_modules/react-native-safari-view", "$(SRCROOT)/../node_modules/react-native-audio/ios", "$(SRCROOT)/../node_modules/react-native-fast-image/ios/FastImage/**", + "$(SRCROOT)/../node_modules/react-native-i18n/ios", ); INFOPLIST_FILE = RocketChatRNTests/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 8.0; @@ -1832,6 +1915,8 @@ "\"$(SRCROOT)/$(TARGET_NAME)\"", "\"$(SRCROOT)/$(TARGET_NAME)\"", "\"$(SRCROOT)/$(TARGET_NAME)\"", + "\"$(SRCROOT)/$(TARGET_NAME)\"", + "\"$(SRCROOT)/$(TARGET_NAME)\"", ); OTHER_LDFLAGS = ( "-ObjC", @@ -1873,6 +1958,7 @@ "$(SRCROOT)/../node_modules/react-native-audio/ios", "$(SRCROOT)/../../../react-native/React/**", "$(SRCROOT)/../node_modules/react-native-fast-image/ios/FastImage/**", + "$(SRCROOT)/../node_modules/react-native-i18n/ios", ); INFOPLIST_FILE = RocketChatRN/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; @@ -1919,6 +2005,7 @@ "$(SRCROOT)/../node_modules/react-native-audio/ios", "$(SRCROOT)/../../../react-native/React/**", "$(SRCROOT)/../node_modules/react-native-fast-image/ios/FastImage/**", + "$(SRCROOT)/../node_modules/react-native-i18n/ios", ); INFOPLIST_FILE = RocketChatRN/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; @@ -1963,6 +2050,7 @@ "$(SRCROOT)/../node_modules/react-native-safari-view", "$(SRCROOT)/../node_modules/react-native-audio/ios", "$(SRCROOT)/../node_modules/react-native-fast-image/ios/FastImage/**", + "$(SRCROOT)/../node_modules/react-native-i18n/ios", ); INFOPLIST_FILE = "RocketChatRN-tvOS/Info.plist"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; @@ -1976,6 +2064,8 @@ "\"$(SRCROOT)/$(TARGET_NAME)\"", "\"$(SRCROOT)/$(TARGET_NAME)\"", "\"$(SRCROOT)/$(TARGET_NAME)\"", + "\"$(SRCROOT)/$(TARGET_NAME)\"", + "\"$(SRCROOT)/$(TARGET_NAME)\"", ); OTHER_LDFLAGS = ( "-ObjC", @@ -2017,6 +2107,7 @@ "$(SRCROOT)/../node_modules/react-native-safari-view", "$(SRCROOT)/../node_modules/react-native-audio/ios", "$(SRCROOT)/../node_modules/react-native-fast-image/ios/FastImage/**", + "$(SRCROOT)/../node_modules/react-native-i18n/ios", ); INFOPLIST_FILE = "RocketChatRN-tvOS/Info.plist"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; @@ -2030,6 +2121,8 @@ "\"$(SRCROOT)/$(TARGET_NAME)\"", "\"$(SRCROOT)/$(TARGET_NAME)\"", "\"$(SRCROOT)/$(TARGET_NAME)\"", + "\"$(SRCROOT)/$(TARGET_NAME)\"", + "\"$(SRCROOT)/$(TARGET_NAME)\"", ); OTHER_LDFLAGS = ( "-ObjC", @@ -2067,6 +2160,8 @@ "\"$(SRCROOT)/$(TARGET_NAME)\"", "\"$(SRCROOT)/$(TARGET_NAME)\"", "\"$(SRCROOT)/$(TARGET_NAME)\"", + "\"$(SRCROOT)/$(TARGET_NAME)\"", + "\"$(SRCROOT)/$(TARGET_NAME)\"", ); PRODUCT_BUNDLE_IDENTIFIER = "com.facebook.REACT.RocketChatRN-tvOSTests"; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -2100,6 +2195,8 @@ "\"$(SRCROOT)/$(TARGET_NAME)\"", "\"$(SRCROOT)/$(TARGET_NAME)\"", "\"$(SRCROOT)/$(TARGET_NAME)\"", + "\"$(SRCROOT)/$(TARGET_NAME)\"", + "\"$(SRCROOT)/$(TARGET_NAME)\"", ); PRODUCT_BUNDLE_IDENTIFIER = "com.facebook.REACT.RocketChatRN-tvOSTests"; PRODUCT_NAME = "$(TARGET_NAME)"; diff --git a/package-lock.json b/package-lock.json index 62ea43eebb99d8bf26c47738d2d7ec273d6dce4c..b8dbfd33f48735522f2db1496b1dac6d0ed076c2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8852,6 +8852,11 @@ "resolved": "https://registry.npmjs.org/hyphenate-style-name/-/hyphenate-style-name-1.0.2.tgz", "integrity": "sha1-MRYKNpMK2vH8BMYHT360FGXU7Es=" }, + "i18n-js": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/i18n-js/-/i18n-js-3.0.3.tgz", + "integrity": "sha512-u144MQhV/8mz4Y5wP86SQAWMwS8gpe/JavIa9hugSI4WreezGgbhJPdk2Q60KcdIltKLiNefGtHNh1N8SSmQqQ==" + }, "iconv-lite": { "version": "0.4.19", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.19.tgz", @@ -14806,6 +14811,14 @@ "prop-types": "15.6.1" } }, + "react-native-i18n": { + "version": "2.0.12", + "resolved": "https://registry.npmjs.org/react-native-i18n/-/react-native-i18n-2.0.12.tgz", + "integrity": "sha512-2eTUk7BVZP5knthCmVt6y7rePFwrxXl4ym2I20e91oTYJnKM22sAjQMnLhRCYZWC4ucRBbe2pUp63uxNdTkkQw==", + "requires": { + "i18n-js": "3.0.3" + } + }, "react-native-image-picker": { "version": "0.26.10", "resolved": "https://registry.npmjs.org/react-native-image-picker/-/react-native-image-picker-0.26.10.tgz", diff --git a/package.json b/package.json index 60b026184817bf043d41f9e05b06f3e1ab0ef1c9..8e6b4dda772216a6c3c1be795373793405b4310f 100644 --- a/package.json +++ b/package.json @@ -47,6 +47,7 @@ "react-native-fabric": "^0.5.1", "react-native-fast-image": "^4.0.14", "react-native-fetch-blob": "^0.10.8", + "react-native-i18n": "^2.0.12", "react-native-image-picker": "^0.26.10", "react-native-keyboard-aware-scroll-view": "^0.5.0", "react-native-keyboard-input": "git+https://github.com/RocketChat/react-native-keyboard-input.git",