[NEW] Auto-translate (#1012)
* Update realm * View original and translate working * Read AutoTranslate_Enabled setting * RocketChat.canAutoTranslate() * AutoTranslateView * Save language * Auto-translate switch * Translate message
This commit is contained in:
parent
cfa126914c
commit
b3986b98b5
|
@ -1,4 +1,4 @@
|
||||||
import { isIOS } from '../utils/deviceInfo';
|
import { isIOS, isAndroid } from '../utils/deviceInfo';
|
||||||
|
|
||||||
export const COLOR_DANGER = '#f5455c';
|
export const COLOR_DANGER = '#f5455c';
|
||||||
export const COLOR_SUCCESS = '#2de0a5';
|
export const COLOR_SUCCESS = '#2de0a5';
|
||||||
|
@ -25,3 +25,8 @@ export const HEADER_BACKGROUND = isIOS ? '#f8f8f8' : '#2F343D';
|
||||||
export const HEADER_TITLE = isIOS ? COLOR_TITLE : COLOR_WHITE;
|
export const HEADER_TITLE = isIOS ? COLOR_TITLE : COLOR_WHITE;
|
||||||
export const HEADER_BACK = isIOS ? COLOR_PRIMARY : COLOR_WHITE;
|
export const HEADER_BACK = isIOS ? COLOR_PRIMARY : COLOR_WHITE;
|
||||||
export const HEADER_TINT = isIOS ? COLOR_PRIMARY : COLOR_WHITE;
|
export const HEADER_TINT = isIOS ? COLOR_PRIMARY : COLOR_WHITE;
|
||||||
|
|
||||||
|
export const SWITCH_TRACK_COLOR = {
|
||||||
|
false: isAndroid ? COLOR_DANGER : null,
|
||||||
|
true: COLOR_SUCCESS
|
||||||
|
};
|
||||||
|
|
|
@ -70,5 +70,8 @@ export default {
|
||||||
},
|
},
|
||||||
API_Gitlab_URL: {
|
API_Gitlab_URL: {
|
||||||
type: 'valueAsString'
|
type: 'valueAsString'
|
||||||
|
},
|
||||||
|
AutoTranslate_Enabled: {
|
||||||
|
type: 'valueAsBoolean'
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -15,9 +15,11 @@ import {
|
||||||
} from '../actions/messages';
|
} from '../actions/messages';
|
||||||
import { vibrate } from '../utils/vibration';
|
import { vibrate } from '../utils/vibration';
|
||||||
import RocketChat from '../lib/rocketchat';
|
import RocketChat from '../lib/rocketchat';
|
||||||
|
import database from '../lib/realm';
|
||||||
import I18n from '../i18n';
|
import I18n from '../i18n';
|
||||||
import log from '../utils/log';
|
import log from '../utils/log';
|
||||||
import Navigation from '../lib/Navigation';
|
import Navigation from '../lib/Navigation';
|
||||||
|
import { getMessageTranslation } from './message/utils';
|
||||||
|
|
||||||
@connect(
|
@connect(
|
||||||
state => ({
|
state => ({
|
||||||
|
@ -46,7 +48,7 @@ export default class MessageActions extends React.Component {
|
||||||
room: PropTypes.object.isRequired,
|
room: PropTypes.object.isRequired,
|
||||||
actionMessage: PropTypes.object,
|
actionMessage: PropTypes.object,
|
||||||
toast: PropTypes.element,
|
toast: PropTypes.element,
|
||||||
// user: PropTypes.object.isRequired,
|
user: PropTypes.object,
|
||||||
deleteRequest: PropTypes.func.isRequired,
|
deleteRequest: PropTypes.func.isRequired,
|
||||||
editInit: PropTypes.func.isRequired,
|
editInit: PropTypes.func.isRequired,
|
||||||
toggleStarRequest: PropTypes.func.isRequired,
|
toggleStarRequest: PropTypes.func.isRequired,
|
||||||
|
@ -127,6 +129,12 @@ export default class MessageActions extends React.Component {
|
||||||
this.READ_RECEIPT_INDEX = this.options.length - 1;
|
this.READ_RECEIPT_INDEX = this.options.length - 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Toggle Auto-translate
|
||||||
|
if (props.room.autoTranslate && props.actionMessage.u && props.actionMessage.u._id !== props.user.id) {
|
||||||
|
this.options.push(I18n.t(props.actionMessage.autoTranslate ? 'View_Original' : 'Translate'));
|
||||||
|
this.TOGGLE_TRANSLATION_INDEX = this.options.length - 1;
|
||||||
|
}
|
||||||
|
|
||||||
// Report
|
// Report
|
||||||
this.options.push(I18n.t('Report'));
|
this.options.push(I18n.t('Report'));
|
||||||
this.REPORT_INDEX = this.options.length - 1;
|
this.REPORT_INDEX = this.options.length - 1;
|
||||||
|
@ -326,6 +334,23 @@ export default class MessageActions extends React.Component {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
handleToggleTranslation = async() => {
|
||||||
|
const { actionMessage, room } = this.props;
|
||||||
|
try {
|
||||||
|
const message = database.objectForPrimaryKey('messages', actionMessage._id);
|
||||||
|
database.write(() => {
|
||||||
|
message.autoTranslate = !message.autoTranslate;
|
||||||
|
message._updatedAt = new Date();
|
||||||
|
});
|
||||||
|
const translatedMessage = getMessageTranslation(message, room.autoTranslateLanguage);
|
||||||
|
if (!translatedMessage) {
|
||||||
|
await RocketChat.translateMessage(actionMessage, room.autoTranslateLanguage);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
log('err_toggle_translation', err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
handleActionPress = (actionIndex) => {
|
handleActionPress = (actionIndex) => {
|
||||||
if (actionIndex) {
|
if (actionIndex) {
|
||||||
switch (actionIndex) {
|
switch (actionIndex) {
|
||||||
|
@ -365,6 +390,9 @@ export default class MessageActions extends React.Component {
|
||||||
case this.READ_RECEIPT_INDEX:
|
case this.READ_RECEIPT_INDEX:
|
||||||
this.handleReadReceipt();
|
this.handleReadReceipt();
|
||||||
break;
|
break;
|
||||||
|
case this.TOGGLE_TRANSLATION_INDEX:
|
||||||
|
this.handleToggleTranslation();
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,7 @@ import { KeyboardUtils } from 'react-native-keyboard-input';
|
||||||
|
|
||||||
import Message from './Message';
|
import Message from './Message';
|
||||||
import debounce from '../../utils/debounce';
|
import debounce from '../../utils/debounce';
|
||||||
import { SYSTEM_MESSAGES, getCustomEmoji } from './utils';
|
import { SYSTEM_MESSAGES, getCustomEmoji, getMessageTranslation } from './utils';
|
||||||
import messagesStatus from '../../constants/messagesStatus';
|
import messagesStatus from '../../constants/messagesStatus';
|
||||||
|
|
||||||
export default class MessageContainer extends React.Component {
|
export default class MessageContainer extends React.Component {
|
||||||
|
@ -27,6 +27,8 @@ export default class MessageContainer extends React.Component {
|
||||||
isReadReceiptEnabled: PropTypes.bool,
|
isReadReceiptEnabled: PropTypes.bool,
|
||||||
useRealName: PropTypes.bool,
|
useRealName: PropTypes.bool,
|
||||||
useMarkdown: PropTypes.bool,
|
useMarkdown: PropTypes.bool,
|
||||||
|
autoTranslateRoom: PropTypes.bool,
|
||||||
|
autoTranslateLanguage: PropTypes.string,
|
||||||
status: PropTypes.number,
|
status: PropTypes.number,
|
||||||
onLongPress: PropTypes.func,
|
onLongPress: PropTypes.func,
|
||||||
onReactionPress: PropTypes.func,
|
onReactionPress: PropTypes.func,
|
||||||
|
@ -49,12 +51,15 @@ export default class MessageContainer extends React.Component {
|
||||||
|
|
||||||
shouldComponentUpdate(nextProps) {
|
shouldComponentUpdate(nextProps) {
|
||||||
const {
|
const {
|
||||||
status, item, _updatedAt
|
status, item, _updatedAt, autoTranslateRoom
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
if (status !== nextProps.status) {
|
if (status !== nextProps.status) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
if (autoTranslateRoom !== nextProps.autoTranslateRoom) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
if (item.tmsg !== nextProps.item.tmsg) {
|
if (item.tmsg !== nextProps.item.tmsg) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -191,16 +196,23 @@ export default class MessageContainer extends React.Component {
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const {
|
const {
|
||||||
item, user, style, archived, baseUrl, useRealName, broadcast, fetchThreadName, customThreadTimeFormat, onOpenFileModal, timeFormat, useMarkdown, isReadReceiptEnabled
|
item, user, style, archived, baseUrl, useRealName, broadcast, fetchThreadName, customThreadTimeFormat, onOpenFileModal, timeFormat, useMarkdown, isReadReceiptEnabled, autoTranslateRoom, autoTranslateLanguage
|
||||||
} = this.props;
|
} = this.props;
|
||||||
const {
|
const {
|
||||||
_id, msg, ts, attachments, urls, reactions, t, avatar, u, alias, editedBy, role, drid, dcount, dlm, tmid, tcount, tlm, tmsg, mentions, channels, unread
|
_id, msg, ts, attachments, urls, reactions, t, avatar, u, alias, editedBy, role, drid, dcount, dlm, tmid, tcount, tlm, tmsg, mentions, channels, unread, autoTranslate: autoTranslateMessage
|
||||||
} = item;
|
} = item;
|
||||||
|
|
||||||
|
let message = msg;
|
||||||
|
// "autoTranslateRoom" and "autoTranslateLanguage" are properties from the subscription
|
||||||
|
// "autoTranslateMessage" is a toggle between "View Original" and "Translate" state
|
||||||
|
if (autoTranslateRoom && autoTranslateMessage) {
|
||||||
|
message = getMessageTranslation(item, autoTranslateLanguage) || message;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Message
|
<Message
|
||||||
id={_id}
|
id={_id}
|
||||||
msg={msg}
|
msg={message}
|
||||||
author={u}
|
author={u}
|
||||||
ts={ts}
|
ts={ts}
|
||||||
type={t}
|
type={t}
|
||||||
|
|
|
@ -114,3 +114,15 @@ export const getCustomEmoji = (content) => {
|
||||||
});
|
});
|
||||||
return findByAlias;
|
return findByAlias;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const getMessageTranslation = (message, autoTranslateLanguage) => {
|
||||||
|
if (!autoTranslateLanguage) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const { translations } = message;
|
||||||
|
if (translations) {
|
||||||
|
const translation = translations.find(trans => trans.language === autoTranslateLanguage);
|
||||||
|
return translation && translation.value;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
|
@ -99,6 +99,7 @@ export default {
|
||||||
Are_you_sure_question_mark: 'Are you sure?',
|
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}}?',
|
Are_you_sure_you_want_to_leave_the_room: 'Are you sure you want to leave the room {{room}}?',
|
||||||
Authenticating: 'Authenticating',
|
Authenticating: 'Authenticating',
|
||||||
|
Auto_Translate: 'Auto-Translate',
|
||||||
Avatar_changed_successfully: 'Avatar changed successfully!',
|
Avatar_changed_successfully: 'Avatar changed successfully!',
|
||||||
Avatar_Url: 'Avatar URL',
|
Avatar_Url: 'Avatar URL',
|
||||||
Away: 'Away',
|
Away: 'Away',
|
||||||
|
@ -155,6 +156,7 @@ export default {
|
||||||
Email_or_password_field_is_empty: 'Email or password field is empty',
|
Email_or_password_field_is_empty: 'Email or password field is empty',
|
||||||
Email: 'Email',
|
Email: 'Email',
|
||||||
email: 'e-mail',
|
email: 'e-mail',
|
||||||
|
Enable_Auto_Translate: 'Enable Auto-Translate',
|
||||||
Enable_markdown: 'Enable markdown',
|
Enable_markdown: 'Enable markdown',
|
||||||
Enable_notifications: 'Enable notifications',
|
Enable_notifications: 'Enable notifications',
|
||||||
Everyone_can_access_this_channel: 'Everyone can access this channel',
|
Everyone_can_access_this_channel: 'Everyone can access this channel',
|
||||||
|
@ -343,6 +345,7 @@ export default {
|
||||||
Timezone: 'Timezone',
|
Timezone: 'Timezone',
|
||||||
topic: 'topic',
|
topic: 'topic',
|
||||||
Topic: 'Topic',
|
Topic: 'Topic',
|
||||||
|
Translate: 'Translate',
|
||||||
Try_again: 'Try again',
|
Try_again: 'Try again',
|
||||||
Two_Factor_Authentication: 'Two-factor Authentication',
|
Two_Factor_Authentication: 'Two-factor Authentication',
|
||||||
Type_the_channel_name_here: 'Type the channel name here',
|
Type_the_channel_name_here: 'Type the channel name here',
|
||||||
|
@ -374,6 +377,7 @@ export default {
|
||||||
Username_or_email: 'Username or email',
|
Username_or_email: 'Username or email',
|
||||||
Validating: 'Validating',
|
Validating: 'Validating',
|
||||||
Video_call: 'Video call',
|
Video_call: 'Video call',
|
||||||
|
View_Original: 'View Original',
|
||||||
Voice_call: 'Voice call',
|
Voice_call: 'Voice call',
|
||||||
Welcome: 'Welcome',
|
Welcome: 'Welcome',
|
||||||
Welcome_to_RocketChat: 'Welcome to Rocket.Chat',
|
Welcome_to_RocketChat: 'Welcome to Rocket.Chat',
|
||||||
|
|
|
@ -33,6 +33,7 @@ import SearchMessagesView from './views/SearchMessagesView';
|
||||||
import ReadReceiptsView from './views/ReadReceiptView';
|
import ReadReceiptsView from './views/ReadReceiptView';
|
||||||
import ThreadMessagesView from './views/ThreadMessagesView';
|
import ThreadMessagesView from './views/ThreadMessagesView';
|
||||||
import MessagesView from './views/MessagesView';
|
import MessagesView from './views/MessagesView';
|
||||||
|
import AutoTranslateView from './views/AutoTranslateView';
|
||||||
import SelectedUsersView from './views/SelectedUsersView';
|
import SelectedUsersView from './views/SelectedUsersView';
|
||||||
import CreateChannelView from './views/CreateChannelView';
|
import CreateChannelView from './views/CreateChannelView';
|
||||||
import LegalView from './views/LegalView';
|
import LegalView from './views/LegalView';
|
||||||
|
@ -116,6 +117,7 @@ const ChatsStack = createStackNavigator({
|
||||||
SelectedUsersView,
|
SelectedUsersView,
|
||||||
ThreadMessagesView,
|
ThreadMessagesView,
|
||||||
MessagesView,
|
MessagesView,
|
||||||
|
AutoTranslateView,
|
||||||
ReadReceiptsView,
|
ReadReceiptsView,
|
||||||
DirectoryView
|
DirectoryView
|
||||||
}, {
|
}, {
|
||||||
|
|
|
@ -36,6 +36,9 @@ export default (msg) => {
|
||||||
if (!Array.isArray(msg.reactions)) {
|
if (!Array.isArray(msg.reactions)) {
|
||||||
msg.reactions = Object.keys(msg.reactions).map(key => ({ _id: `${ msg._id }${ key }`, emoji: key, usernames: msg.reactions[key].usernames }));
|
msg.reactions = Object.keys(msg.reactions).map(key => ({ _id: `${ msg._id }${ key }`, emoji: key, usernames: msg.reactions[key].usernames }));
|
||||||
}
|
}
|
||||||
|
if (msg.translations && Object.keys(msg.translations).length) {
|
||||||
|
msg.translations = Object.keys(msg.translations).map(key => ({ _id: `${ msg._id }${ key }`, language: key, value: msg.translations[key] }));
|
||||||
|
}
|
||||||
msg.urls = msg.urls ? parseUrls(msg.urls) : [];
|
msg.urls = msg.urls ? parseUrls(msg.urls) : [];
|
||||||
msg._updatedAt = new Date();
|
msg._updatedAt = new Date();
|
||||||
// loadHistory returns msg.starred as object
|
// loadHistory returns msg.starred as object
|
||||||
|
|
|
@ -96,7 +96,9 @@ const subscriptionSchema = {
|
||||||
broadcast: { type: 'bool', optional: true },
|
broadcast: { type: 'bool', optional: true },
|
||||||
prid: { type: 'string', optional: true },
|
prid: { type: 'string', optional: true },
|
||||||
draftMessage: { type: 'string', optional: true },
|
draftMessage: { type: 'string', optional: true },
|
||||||
lastThreadSync: 'date?'
|
lastThreadSync: 'date?',
|
||||||
|
autoTranslate: 'bool?',
|
||||||
|
autoTranslateLanguage: 'string?'
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -171,6 +173,16 @@ const messagesReactionsSchema = {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const messagesTranslationsSchema = {
|
||||||
|
name: 'messagesTranslations',
|
||||||
|
primaryKey: '_id',
|
||||||
|
properties: {
|
||||||
|
_id: 'string',
|
||||||
|
language: 'string',
|
||||||
|
value: 'string'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const messagesEditedBySchema = {
|
const messagesEditedBySchema = {
|
||||||
name: 'messagesEditedBy',
|
name: 'messagesEditedBy',
|
||||||
primaryKey: '_id',
|
primaryKey: '_id',
|
||||||
|
@ -212,7 +224,9 @@ const messagesSchema = {
|
||||||
replies: 'string[]',
|
replies: 'string[]',
|
||||||
mentions: { type: 'list', objectType: 'users' },
|
mentions: { type: 'list', objectType: 'users' },
|
||||||
channels: { type: 'list', objectType: 'rooms' },
|
channels: { type: 'list', objectType: 'rooms' },
|
||||||
unread: { type: 'bool', optional: true }
|
unread: { type: 'bool', optional: true },
|
||||||
|
autoTranslate: { type: 'bool', default: false },
|
||||||
|
translations: { type: 'list', objectType: 'messagesTranslations' }
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -246,6 +260,11 @@ const threadsSchema = {
|
||||||
tcount: { type: 'int', optional: true },
|
tcount: { type: 'int', optional: true },
|
||||||
tlm: { type: 'date', optional: true },
|
tlm: { type: 'date', optional: true },
|
||||||
replies: 'string[]',
|
replies: 'string[]',
|
||||||
|
mentions: { type: 'list', objectType: 'users' },
|
||||||
|
channels: { type: 'list', objectType: 'rooms' },
|
||||||
|
unread: { type: 'bool', optional: true },
|
||||||
|
autoTranslate: { type: 'bool', default: false },
|
||||||
|
translations: { type: 'list', objectType: 'messagesTranslations' },
|
||||||
draftMessage: 'string?'
|
draftMessage: 'string?'
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -272,7 +291,13 @@ const threadMessagesSchema = {
|
||||||
starred: { type: 'bool', optional: true },
|
starred: { type: 'bool', optional: true },
|
||||||
editedBy: 'messagesEditedBy',
|
editedBy: 'messagesEditedBy',
|
||||||
reactions: { type: 'list', objectType: 'messagesReactions' },
|
reactions: { type: 'list', objectType: 'messagesReactions' },
|
||||||
role: { type: 'string', optional: true }
|
role: { type: 'string', optional: true },
|
||||||
|
replies: 'string[]',
|
||||||
|
mentions: { type: 'list', objectType: 'users' },
|
||||||
|
channels: { type: 'list', objectType: 'rooms' },
|
||||||
|
unread: { type: 'bool', optional: true },
|
||||||
|
autoTranslate: { type: 'bool', default: false },
|
||||||
|
translations: { type: 'list', objectType: 'messagesTranslations' }
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -374,7 +399,8 @@ const schema = [
|
||||||
messagesReactionsSchema,
|
messagesReactionsSchema,
|
||||||
rolesSchema,
|
rolesSchema,
|
||||||
uploadsSchema,
|
uploadsSchema,
|
||||||
slashCommandSchema
|
slashCommandSchema,
|
||||||
|
messagesTranslationsSchema
|
||||||
];
|
];
|
||||||
|
|
||||||
const inMemorySchema = [usersTypingSchema, activeUsersSchema];
|
const inMemorySchema = [usersTypingSchema, activeUsersSchema];
|
||||||
|
@ -387,9 +413,9 @@ class DB {
|
||||||
userSchema,
|
userSchema,
|
||||||
serversSchema
|
serversSchema
|
||||||
],
|
],
|
||||||
schemaVersion: 8,
|
schemaVersion: 9,
|
||||||
migration: (oldRealm, newRealm) => {
|
migration: (oldRealm, newRealm) => {
|
||||||
if (oldRealm.schemaVersion >= 1 && newRealm.schemaVersion <= 8) {
|
if (oldRealm.schemaVersion >= 1 && newRealm.schemaVersion <= 9) {
|
||||||
const newServers = newRealm.objects('servers');
|
const newServers = newRealm.objects('servers');
|
||||||
|
|
||||||
// eslint-disable-next-line no-plusplus
|
// eslint-disable-next-line no-plusplus
|
||||||
|
@ -444,9 +470,9 @@ class DB {
|
||||||
return this.databases.activeDB = new Realm({
|
return this.databases.activeDB = new Realm({
|
||||||
path: `${ path }.realm`,
|
path: `${ path }.realm`,
|
||||||
schema,
|
schema,
|
||||||
schemaVersion: 12,
|
schemaVersion: 13,
|
||||||
migration: (oldRealm, newRealm) => {
|
migration: (oldRealm, newRealm) => {
|
||||||
if (oldRealm.schemaVersion >= 3 && newRealm.schemaVersion <= 11) {
|
if (oldRealm.schemaVersion >= 3 && newRealm.schemaVersion <= 13) {
|
||||||
const newSubs = newRealm.objects('subscriptions');
|
const newSubs = newRealm.objects('subscriptions');
|
||||||
newRealm.delete(newSubs);
|
newRealm.delete(newSubs);
|
||||||
const newMessages = newRealm.objects('messages');
|
const newMessages = newRealm.objects('messages');
|
||||||
|
|
|
@ -881,6 +881,31 @@ const RocketChat = {
|
||||||
return this.sdk.get('directory', {
|
return this.sdk.get('directory', {
|
||||||
query, count, offset, sort
|
query, count, offset, sort
|
||||||
});
|
});
|
||||||
|
},
|
||||||
|
canAutoTranslate() {
|
||||||
|
try {
|
||||||
|
const AutoTranslate_Enabled = reduxStore.getState().settings && reduxStore.getState().settings.AutoTranslate_Enabled;
|
||||||
|
if (!AutoTranslate_Enabled) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const autoTranslatePermission = database.objectForPrimaryKey('permissions', 'auto-translate');
|
||||||
|
const userRoles = (reduxStore.getState().login.user && reduxStore.getState().login.user.roles) || [];
|
||||||
|
return autoTranslatePermission.roles.some(role => userRoles.includes(role));
|
||||||
|
} catch (error) {
|
||||||
|
log('err_can_auto_translate', error);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
saveAutoTranslate({
|
||||||
|
rid, field, value, options
|
||||||
|
}) {
|
||||||
|
return this.sdk.methodCall('autoTranslate.saveSettings', rid, field, value, options);
|
||||||
|
},
|
||||||
|
getSupportedLanguagesAutoTranslate() {
|
||||||
|
return this.sdk.methodCall('autoTranslate.getSupportedLanguages', 'en');
|
||||||
|
},
|
||||||
|
translateMessage(message, targetLanguage) {
|
||||||
|
return this.sdk.methodCall('autoTranslate.translateMessage', message, targetLanguage);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,156 @@
|
||||||
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import {
|
||||||
|
FlatList, Switch, View, StyleSheet
|
||||||
|
} from 'react-native';
|
||||||
|
import { SafeAreaView, ScrollView } from 'react-navigation';
|
||||||
|
|
||||||
|
import RocketChat from '../../lib/rocketchat';
|
||||||
|
import I18n from '../../i18n';
|
||||||
|
// import log from '../../utils/log';
|
||||||
|
import StatusBar from '../../containers/StatusBar';
|
||||||
|
import { CustomIcon } from '../../lib/Icons';
|
||||||
|
import sharedStyles from '../Styles';
|
||||||
|
import ListItem from '../../containers/ListItem';
|
||||||
|
import Separator from '../../containers/Separator';
|
||||||
|
import {
|
||||||
|
SWITCH_TRACK_COLOR, COLOR_BACKGROUND_CONTAINER, COLOR_WHITE, COLOR_SEPARATOR
|
||||||
|
} from '../../constants/colors';
|
||||||
|
import scrollPersistTaps from '../../utils/scrollPersistTaps';
|
||||||
|
import database from '../../lib/realm';
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
contentContainerStyle: {
|
||||||
|
borderColor: COLOR_SEPARATOR,
|
||||||
|
borderTopWidth: StyleSheet.hairlineWidth,
|
||||||
|
borderBottomWidth: StyleSheet.hairlineWidth,
|
||||||
|
backgroundColor: COLOR_WHITE,
|
||||||
|
marginTop: 10,
|
||||||
|
paddingBottom: 30
|
||||||
|
},
|
||||||
|
sectionSeparator: {
|
||||||
|
...sharedStyles.separatorVertical,
|
||||||
|
backgroundColor: COLOR_BACKGROUND_CONTAINER,
|
||||||
|
height: 10
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const SectionSeparator = React.memo(() => <View style={styles.sectionSeparator} />);
|
||||||
|
|
||||||
|
export default class AutoTranslateView extends React.Component {
|
||||||
|
static navigationOptions = () => ({
|
||||||
|
title: I18n.t('Auto_Translate')
|
||||||
|
})
|
||||||
|
|
||||||
|
static propTypes = {
|
||||||
|
navigation: PropTypes.object
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.rid = props.navigation.getParam('rid');
|
||||||
|
this.rooms = database.objects('subscriptions').filtered('rid = $0', this.rid);
|
||||||
|
this.state = {
|
||||||
|
languages: [],
|
||||||
|
selectedLanguage: this.rooms[0].autoTranslateLanguage,
|
||||||
|
enableAutoTranslate: this.rooms[0].autoTranslate
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async componentDidMount() {
|
||||||
|
try {
|
||||||
|
const languages = await RocketChat.getSupportedLanguagesAutoTranslate();
|
||||||
|
this.setState({ languages });
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
toggleAutoTranslate = async() => {
|
||||||
|
const { enableAutoTranslate } = this.state;
|
||||||
|
try {
|
||||||
|
await RocketChat.saveAutoTranslate({
|
||||||
|
rid: this.rid,
|
||||||
|
field: 'autoTranslate',
|
||||||
|
value: enableAutoTranslate ? '0' : '1',
|
||||||
|
options: { defaultLanguage: 'en' }
|
||||||
|
});
|
||||||
|
this.setState({ enableAutoTranslate: !enableAutoTranslate });
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
saveAutoTranslateLanguage = async(language) => {
|
||||||
|
try {
|
||||||
|
await RocketChat.saveAutoTranslate({
|
||||||
|
rid: this.rid,
|
||||||
|
field: 'autoTranslateLanguage',
|
||||||
|
value: language
|
||||||
|
});
|
||||||
|
this.setState({ selectedLanguage: language });
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
renderSeparator = () => <Separator />
|
||||||
|
|
||||||
|
renderIcon = () => <CustomIcon name='check' size={20} style={sharedStyles.colorPrimary} />
|
||||||
|
|
||||||
|
renderSwitch = () => {
|
||||||
|
const { enableAutoTranslate } = this.state;
|
||||||
|
return (
|
||||||
|
<Switch
|
||||||
|
value={enableAutoTranslate}
|
||||||
|
trackColor={SWITCH_TRACK_COLOR}
|
||||||
|
onValueChange={this.toggleAutoTranslate}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
renderItem = ({ item }) => {
|
||||||
|
const { selectedLanguage } = this.state;
|
||||||
|
const { language, name } = item;
|
||||||
|
const isSelected = selectedLanguage === language;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ListItem
|
||||||
|
title={name || language}
|
||||||
|
onPress={() => this.saveAutoTranslateLanguage(language)}
|
||||||
|
testID={`auto-translate-view-${ language }`}
|
||||||
|
right={isSelected ? this.renderIcon : null}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { languages } = this.state;
|
||||||
|
return (
|
||||||
|
<SafeAreaView style={sharedStyles.listSafeArea} testID='auto-translate-view' forceInset={{ bottom: 'never' }}>
|
||||||
|
<StatusBar />
|
||||||
|
<ScrollView
|
||||||
|
{...scrollPersistTaps}
|
||||||
|
contentContainerStyle={styles.contentContainerStyle}
|
||||||
|
testID='auto-translate-view-list'
|
||||||
|
>
|
||||||
|
<ListItem
|
||||||
|
title={I18n.t('Enable_Auto_Translate')}
|
||||||
|
testID='auto-translate-view-switch'
|
||||||
|
right={() => this.renderSwitch()}
|
||||||
|
/>
|
||||||
|
<SectionSeparator />
|
||||||
|
<FlatList
|
||||||
|
data={languages}
|
||||||
|
extraData={this.state}
|
||||||
|
keyExtractor={item => item.language}
|
||||||
|
renderItem={this.renderItem}
|
||||||
|
ItemSeparatorComponent={this.renderSeparator}
|
||||||
|
/>
|
||||||
|
</ScrollView>
|
||||||
|
</SafeAreaView>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
console.disableYellowBox = true;
|
|
@ -16,10 +16,9 @@ import scrollPersistTaps from '../utils/scrollPersistTaps';
|
||||||
import I18n from '../i18n';
|
import I18n from '../i18n';
|
||||||
import UserItem from '../presentation/UserItem';
|
import UserItem from '../presentation/UserItem';
|
||||||
import { showErrorAlert } from '../utils/info';
|
import { showErrorAlert } from '../utils/info';
|
||||||
import { isAndroid } from '../utils/deviceInfo';
|
|
||||||
import { CustomHeaderButtons, Item } from '../containers/HeaderButton';
|
import { CustomHeaderButtons, Item } from '../containers/HeaderButton';
|
||||||
import StatusBar from '../containers/StatusBar';
|
import StatusBar from '../containers/StatusBar';
|
||||||
import { COLOR_TEXT_DESCRIPTION, COLOR_WHITE } from '../constants/colors';
|
import { COLOR_TEXT_DESCRIPTION, COLOR_WHITE, SWITCH_TRACK_COLOR } from '../constants/colors';
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
container: {
|
container: {
|
||||||
|
@ -245,8 +244,7 @@ export default class CreateChannelView extends React.Component {
|
||||||
value={value}
|
value={value}
|
||||||
onValueChange={onValueChange}
|
onValueChange={onValueChange}
|
||||||
testID={`create-channel-${ id }`}
|
testID={`create-channel-${ id }`}
|
||||||
onTintColor='#2de0a5'
|
trackColor={SWITCH_TRACK_COLOR}
|
||||||
tintColor={isAndroid ? '#f5455c' : null}
|
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
|
|
|
@ -9,6 +9,7 @@ import styles from './styles';
|
||||||
import { CustomIcon } from '../../lib/Icons';
|
import { CustomIcon } from '../../lib/Icons';
|
||||||
import Check from '../../containers/Check';
|
import Check from '../../containers/Check';
|
||||||
import I18n from '../../i18n';
|
import I18n from '../../i18n';
|
||||||
|
import { SWITCH_TRACK_COLOR } from '../../constants/colors';
|
||||||
|
|
||||||
const ANIMATION_DURATION = 200;
|
const ANIMATION_DURATION = 200;
|
||||||
const ANIMATION_PROPS = {
|
const ANIMATION_PROPS = {
|
||||||
|
@ -109,7 +110,7 @@ export default class DirectoryOptions extends PureComponent {
|
||||||
<Text style={styles.dropdownItemText}>{I18n.t('Search_global_users')}</Text>
|
<Text style={styles.dropdownItemText}>{I18n.t('Search_global_users')}</Text>
|
||||||
<Text style={styles.dropdownItemDescription}>{I18n.t('Search_global_users_description')}</Text>
|
<Text style={styles.dropdownItemDescription}>{I18n.t('Search_global_users_description')}</Text>
|
||||||
</View>
|
</View>
|
||||||
<Switch value={globalUsers} onValueChange={toggleWorkspace} />
|
<Switch value={globalUsers} onValueChange={toggleWorkspace} trackColor={SWITCH_TRACK_COLOR} />
|
||||||
</View>
|
</View>
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
)
|
)
|
||||||
|
|
|
@ -46,7 +46,6 @@ const LANGUAGES = [
|
||||||
}), dispatch => ({
|
}), dispatch => ({
|
||||||
setUser: params => dispatch(setUserAction(params))
|
setUser: params => dispatch(setUserAction(params))
|
||||||
}))
|
}))
|
||||||
/** @extends React.Component */
|
|
||||||
export default class LanguageView extends React.Component {
|
export default class LanguageView extends React.Component {
|
||||||
static navigationOptions = () => ({
|
static navigationOptions = () => ({
|
||||||
title: I18n.t('Change_Language')
|
title: I18n.t('Change_Language')
|
||||||
|
|
|
@ -60,7 +60,8 @@ export default class RoomActionsView extends React.Component {
|
||||||
membersCount: 0,
|
membersCount: 0,
|
||||||
member: {},
|
member: {},
|
||||||
joined: this.rooms.length > 0,
|
joined: this.rooms.length > 0,
|
||||||
canViewMembers: false
|
canViewMembers: false,
|
||||||
|
canAutoTranslate: false
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -89,6 +90,10 @@ export default class RoomActionsView extends React.Component {
|
||||||
} else if (room.t === 'd') {
|
} else if (room.t === 'd') {
|
||||||
this.updateRoomMember();
|
this.updateRoomMember();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const canAutoTranslate = RocketChat.canAutoTranslate();
|
||||||
|
this.setState({ canAutoTranslate });
|
||||||
|
|
||||||
safeAddListener(this.rooms, this.updateRoom);
|
safeAddListener(this.rooms, this.updateRoom);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -169,7 +174,7 @@ export default class RoomActionsView extends React.Component {
|
||||||
|
|
||||||
get sections() {
|
get sections() {
|
||||||
const {
|
const {
|
||||||
room, membersCount, canViewMembers, joined
|
room, membersCount, canViewMembers, joined, canAutoTranslate
|
||||||
} = this.state;
|
} = this.state;
|
||||||
const {
|
const {
|
||||||
rid, t, blocker, notifications
|
rid, t, blocker, notifications
|
||||||
|
@ -255,6 +260,16 @@ export default class RoomActionsView extends React.Component {
|
||||||
renderItem: this.renderItem
|
renderItem: this.renderItem
|
||||||
}];
|
}];
|
||||||
|
|
||||||
|
if (canAutoTranslate) {
|
||||||
|
sections[2].data.push({
|
||||||
|
icon: 'language',
|
||||||
|
name: I18n.t('Auto_Translate'),
|
||||||
|
route: 'AutoTranslateView',
|
||||||
|
params: { rid },
|
||||||
|
testID: 'room-actions-auto-translate'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
if (t === 'd') {
|
if (t === 'd') {
|
||||||
sections.push({
|
sections.push({
|
||||||
data: [
|
data: [
|
||||||
|
|
|
@ -4,6 +4,7 @@ import PropTypes from 'prop-types';
|
||||||
|
|
||||||
import styles from './styles';
|
import styles from './styles';
|
||||||
import sharedStyles from '../Styles';
|
import sharedStyles from '../Styles';
|
||||||
|
import { SWITCH_TRACK_COLOR } from '../../constants/colors';
|
||||||
|
|
||||||
export default class SwitchContainer extends React.PureComponent {
|
export default class SwitchContainer extends React.PureComponent {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
|
@ -33,6 +34,7 @@ export default class SwitchContainer extends React.PureComponent {
|
||||||
onValueChange={onValueChange}
|
onValueChange={onValueChange}
|
||||||
value={value}
|
value={value}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
|
trackColor={SWITCH_TRACK_COLOR}
|
||||||
testID={testID}
|
testID={testID}
|
||||||
/>
|
/>
|
||||||
<View style={styles.switchLabelContainer}>
|
<View style={styles.switchLabelContainer}>
|
||||||
|
|
|
@ -138,6 +138,7 @@ export default class RoomView extends React.Component {
|
||||||
this.t = props.navigation.getParam('t');
|
this.t = props.navigation.getParam('t');
|
||||||
this.tmid = props.navigation.getParam('tmid');
|
this.tmid = props.navigation.getParam('tmid');
|
||||||
this.rooms = database.objects('subscriptions').filtered('rid = $0', this.rid);
|
this.rooms = database.objects('subscriptions').filtered('rid = $0', this.rid);
|
||||||
|
const canAutoTranslate = RocketChat.canAutoTranslate();
|
||||||
this.state = {
|
this.state = {
|
||||||
joined: this.rooms.length > 0,
|
joined: this.rooms.length > 0,
|
||||||
room: this.rooms[0] || { rid: this.rid, t: this.t },
|
room: this.rooms[0] || { rid: this.rid, t: this.t },
|
||||||
|
@ -145,7 +146,8 @@ export default class RoomView extends React.Component {
|
||||||
photoModalVisible: false,
|
photoModalVisible: false,
|
||||||
reactionsModalVisible: false,
|
reactionsModalVisible: false,
|
||||||
selectedAttachment: {},
|
selectedAttachment: {},
|
||||||
selectedMessage: {}
|
selectedMessage: {},
|
||||||
|
canAutoTranslate
|
||||||
};
|
};
|
||||||
this.beginAnimating = false;
|
this.beginAnimating = false;
|
||||||
this.beginAnimatingTimeout = setTimeout(() => this.beginAnimating = true, 300);
|
this.beginAnimatingTimeout = setTimeout(() => this.beginAnimating = true, 300);
|
||||||
|
@ -180,7 +182,7 @@ export default class RoomView extends React.Component {
|
||||||
|
|
||||||
shouldComponentUpdate(nextProps, nextState) {
|
shouldComponentUpdate(nextProps, nextState) {
|
||||||
const {
|
const {
|
||||||
room, joined, lastOpen, photoModalVisible, reactionsModalVisible
|
room, joined, lastOpen, photoModalVisible, reactionsModalVisible, canAutoTranslate
|
||||||
} = this.state;
|
} = this.state;
|
||||||
const { showActions, showErrorActions, appState } = this.props;
|
const { showActions, showErrorActions, appState } = this.props;
|
||||||
|
|
||||||
|
@ -202,6 +204,8 @@ export default class RoomView extends React.Component {
|
||||||
return true;
|
return true;
|
||||||
} else if (joined !== nextState.joined) {
|
} else if (joined !== nextState.joined) {
|
||||||
return true;
|
return true;
|
||||||
|
} else if (canAutoTranslate !== nextState.canAutoTranslate) {
|
||||||
|
return true;
|
||||||
} else if (showActions !== nextProps.showActions) {
|
} else if (showActions !== nextProps.showActions) {
|
||||||
return true;
|
return true;
|
||||||
} else if (showErrorActions !== nextProps.showErrorActions) {
|
} else if (showErrorActions !== nextProps.showErrorActions) {
|
||||||
|
@ -298,6 +302,11 @@ export default class RoomView extends React.Component {
|
||||||
this.sub = await RocketChat.subscribeRoom(room);
|
this.sub = await RocketChat.subscribeRoom(room);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// We run `canAutoTranslate` again in order to refetch auto translate permission
|
||||||
|
// in case of a missing connection or poor connection on room open
|
||||||
|
const canAutoTranslate = RocketChat.canAutoTranslate();
|
||||||
|
this.setState({ canAutoTranslate });
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log('err_room_init', e);
|
log('err_room_init', e);
|
||||||
|
@ -500,7 +509,7 @@ export default class RoomView extends React.Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
renderItem = (item, previousItem) => {
|
renderItem = (item, previousItem) => {
|
||||||
const { room, lastOpen } = this.state;
|
const { room, lastOpen, canAutoTranslate } = this.state;
|
||||||
const {
|
const {
|
||||||
user, Message_GroupingPeriod, Message_TimeFormat, useRealName, baseUrl, useMarkdown, Message_Read_Receipt_Enabled
|
user, Message_GroupingPeriod, Message_TimeFormat, useRealName, baseUrl, useMarkdown, Message_Read_Receipt_Enabled
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
@ -545,6 +554,8 @@ export default class RoomView extends React.Component {
|
||||||
useRealName={useRealName}
|
useRealName={useRealName}
|
||||||
useMarkdown={useMarkdown}
|
useMarkdown={useMarkdown}
|
||||||
isReadReceiptEnabled={Message_Read_Receipt_Enabled}
|
isReadReceiptEnabled={Message_Read_Receipt_Enabled}
|
||||||
|
autoTranslateRoom={canAutoTranslate && room.autoTranslate}
|
||||||
|
autoTranslateLanguage={room.autoTranslateLanguage}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,7 @@ import PropTypes from 'prop-types';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
|
|
||||||
import { toggleMarkdown as toggleMarkdownAction } from '../../actions/markdown';
|
import { toggleMarkdown as toggleMarkdownAction } from '../../actions/markdown';
|
||||||
import { COLOR_DANGER, COLOR_SUCCESS } from '../../constants/colors';
|
import { SWITCH_TRACK_COLOR } from '../../constants/colors';
|
||||||
import { DrawerButton } from '../../containers/HeaderButton';
|
import { DrawerButton } from '../../containers/HeaderButton';
|
||||||
import StatusBar from '../../containers/StatusBar';
|
import StatusBar from '../../containers/StatusBar';
|
||||||
import ListItem from '../../containers/ListItem';
|
import ListItem from '../../containers/ListItem';
|
||||||
|
@ -14,7 +14,7 @@ import { DisclosureImage } from '../../containers/DisclosureIndicator';
|
||||||
import Separator from '../../containers/Separator';
|
import Separator from '../../containers/Separator';
|
||||||
import I18n from '../../i18n';
|
import I18n from '../../i18n';
|
||||||
import { MARKDOWN_KEY } from '../../lib/rocketchat';
|
import { MARKDOWN_KEY } from '../../lib/rocketchat';
|
||||||
import { getReadableVersion, getDeviceModel, isAndroid } from '../../utils/deviceInfo';
|
import { getReadableVersion, getDeviceModel } from '../../utils/deviceInfo';
|
||||||
import openLink from '../../utils/openLink';
|
import openLink from '../../utils/openLink';
|
||||||
import scrollPersistTaps from '../../utils/scrollPersistTaps';
|
import scrollPersistTaps from '../../utils/scrollPersistTaps';
|
||||||
import { showErrorAlert } from '../../utils/info';
|
import { showErrorAlert } from '../../utils/info';
|
||||||
|
@ -23,10 +23,6 @@ import sharedStyles from '../Styles';
|
||||||
|
|
||||||
const LICENSE_LINK = 'https://github.com/RocketChat/Rocket.Chat.ReactNative/blob/develop/LICENSE';
|
const LICENSE_LINK = 'https://github.com/RocketChat/Rocket.Chat.ReactNative/blob/develop/LICENSE';
|
||||||
const SectionSeparator = React.memo(() => <View style={styles.sectionSeparatorBorder} />);
|
const SectionSeparator = React.memo(() => <View style={styles.sectionSeparatorBorder} />);
|
||||||
const SWITCH_TRACK_COLOR = {
|
|
||||||
false: isAndroid ? COLOR_DANGER : null,
|
|
||||||
true: COLOR_SUCCESS
|
|
||||||
};
|
|
||||||
|
|
||||||
@connect(state => ({
|
@connect(state => ({
|
||||||
server: state.server,
|
server: state.server,
|
||||||
|
|
Loading…
Reference in New Issue