diff --git a/android/app/build.gradle b/android/app/build.gradle index 8179f8603..8c3ef1d6d 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 7883ed1b2..c6fbea41c 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 1259f2ba9..ffb155c5c 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 ae7b75d1f..000000000 --- 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 ( - - offline... - - ); - } - - if (connecting) { - return ( - - Connecting... - - ); - } - - if (authenticating) { - return ( - - Authenticating... - - ); - } - - if (logged) { - return this.props.children; - } - - return ( - - Not logged... - - ); - } -} diff --git a/app/containers/MessageActions.js b/app/containers/MessageActions.js index d126600df..ee63c6ad9 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 ( 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 713564a19..5396fb671 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 113ce2dbe..fea1c26b6 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 ( this.editCancel()} testID='messagebox-cancel-editing' @@ -116,14 +117,14 @@ export default class MessageBox extends React.PureComponent { return !this.state.showEmojiKeyboard ? ( this.openEmoji()} - accessibilityLabel='Open emoji selector' + accessibilityLabel={I18n.t('Open_emoji_selector')} accessibilityTraits='button' name='mood' testID='messagebox-open-emoji' />) : ( 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)} > {item.username} - Notify {item.desc} in this room + {item.username === 'here' ? I18n.t('Notify_active_in_this_room') : I18n.t('Notify_all_in_this_room')} ) 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 a65c016d7..4a8a9eeb5 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 ( 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 df34b7ff1..655338416 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' > - - Logout - + {I18n.t('Logout')} - - Add Server - + {I18n.t('Add_Server')} diff --git a/app/containers/Typing.js b/app/containers/Typing.js index eb667093b..162d462f0 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 8f3f5620f..cde604260 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 ( @@ -86,7 +87,7 @@ export default class ReactionsModal extends React.PureComponent { - {count === 1 ? '1 person' : `${ count } people`} reacted + {count === 1 ? I18n.t('1_person_reacted') : I18n.t('N_people_reacted', { n: count })} { usernames } @@ -113,7 +114,7 @@ export default class ReactionsModal extends React.PureComponent { size={20} onPress={onClose} /> - Reactions + {I18n.t('Reactions')} diff --git a/app/containers/message/index.js b/app/containers/message/index.js index 3aac53d5f..54c31aa49 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 ( { 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: ( navigation.navigate({ key: 'AddServer', routeName: 'AddServer' })} style={{ width: 50, alignItems: 'center' }} - accessibilityLabel='Add server' + accessibilityLabel={I18n.t('Add_Server')} accessibilityTraits='button' > @@ -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 000000000..b130a36bd --- /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 000000000..e2cf96ecb --- /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 e5d4340d4..371ddaf3d 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 f2401d698..243d4e3be 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 0585e833b..0dff63c69 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 ed221fe3e..bad0fcfad 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 { 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()}