more components updated/translated
This commit is contained in:
@@ -8,12 +8,18 @@ var path = require('path');
|
|||||||
var globSync = require('glob').sync;
|
var globSync = require('glob').sync;
|
||||||
var mkdirpSync = require('mkdirp').sync;
|
var mkdirpSync = require('mkdirp').sync;
|
||||||
|
|
||||||
|
const DEFAULT_TRANSLATION = 'en.json';
|
||||||
const MESSAGES_PATTERN = './src/i18n/src/**/*.json';
|
const MESSAGES_PATTERN = './src/i18n/src/**/*.json';
|
||||||
const LANG_DIR = './src/i18n/';
|
const LANG_DIR = './src/i18n/';
|
||||||
const LANG_PATTERN = './src/i18n/*.json';
|
const LANG_PATTERN = './src/i18n/*.json';
|
||||||
const FINAL_FILE = './src/messages.json';
|
const FINAL_FILE = './src/messages.json';
|
||||||
|
|
||||||
// Read message files generated by react-intl-babel-plugin.
|
const MISSING_MARKER = ':!![MISSING]!!:';
|
||||||
|
const OBSOLETE_MARKER = ':!![OBSOLETE]!!:';
|
||||||
|
|
||||||
|
// The react-intl-babel-plugin saves extracted messages
|
||||||
|
// into files, one fine per component. Rewrite them into
|
||||||
|
// a single file en.json.
|
||||||
let messages = globSync(MESSAGES_PATTERN)
|
let messages = globSync(MESSAGES_PATTERN)
|
||||||
.map((filename) => fs.readFileSync(filename, 'utf8'))
|
.map((filename) => fs.readFileSync(filename, 'utf8'))
|
||||||
.map((file) => JSON.parse(file))
|
.map((file) => JSON.parse(file))
|
||||||
@@ -28,17 +34,59 @@ let messages = globSync(MESSAGES_PATTERN)
|
|||||||
return collection;
|
return collection;
|
||||||
}, {});
|
}, {});
|
||||||
|
|
||||||
// Write English translation.
|
let filename = path.join(LANG_DIR, DEFAULT_TRANSLATION);
|
||||||
let filename = path.join(LANG_DIR, 'en.json');
|
|
||||||
mkdirpSync(path.dirname(filename));
|
mkdirpSync(path.dirname(filename));
|
||||||
fs.writeFileSync(filename, JSON.stringify(messages, null, 2));
|
fs.writeFileSync(filename, JSON.stringify(messages, null, 2));
|
||||||
|
|
||||||
|
// Find messages present in en.json but missing in translations and vice versa.
|
||||||
|
// Mark missing or obsolete messages as such in translation files.
|
||||||
|
globSync(LANG_PATTERN)
|
||||||
|
.map((filename) => {
|
||||||
|
const parts = filename.split('/');
|
||||||
|
if (parts[parts.length-1] != DEFAULT_TRANSLATION) {
|
||||||
|
const translated = JSON.parse(fs.readFileSync(filename, 'utf8'));
|
||||||
|
// Find missing messages.
|
||||||
|
Object.entries(messages).forEach((ent) => {
|
||||||
|
if (!translated.hasOwnProperty(ent[0])) {
|
||||||
|
translated[ent[0]] = MISSING_MARKER + ent[1];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
// Find no longer needed messages.
|
||||||
|
Object.entries(translated).forEach((ent) => {
|
||||||
|
if (!messages.hasOwnProperty(ent[0])) {
|
||||||
|
let val = translated[ent[0]];
|
||||||
|
if (val.indexOf(MISSING_MARKER) == 0) {
|
||||||
|
val = val.substring(0, MISSING_MARKER.length);
|
||||||
|
}
|
||||||
|
if (val.indexOf(OBSOLETE_MARKER) != 0) {
|
||||||
|
val = OBSOLETE_MARKER + val;
|
||||||
|
}
|
||||||
|
translated[ent[0]] = val;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
fs.writeFileSync(filename, JSON.stringify(translated, null, 2));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Combine all translations into a single json {"lang_1": {...}, "lang_2: {...}"};
|
||||||
|
// Remove messages marked as missing or obsolete.
|
||||||
|
|
||||||
// Read all translations.
|
// Read all translations.
|
||||||
messages = globSync(LANG_PATTERN)
|
messages = globSync(LANG_PATTERN)
|
||||||
.map((filename) => {
|
.map((filename) => {
|
||||||
const parts = filename.split('/');
|
const parts = filename.split('/');
|
||||||
const locale = parts[parts.length - 1].split('.json')[0];
|
const locale = parts[parts.length - 1].split('.json')[0];
|
||||||
return { [locale]: JSON.parse(fs.readFileSync(filename, 'utf8')) };
|
const translated = JSON.parse(fs.readFileSync(filename, 'utf8'));
|
||||||
|
|
||||||
|
// Strip messages prefixed with MISSING or OBSOLETE markers.
|
||||||
|
for (let id in translated) {
|
||||||
|
if (translated[id].indexOf(OBSOLETE_MARKER) == 0 ||
|
||||||
|
translated[id].indexOf(MISSING_MARKER) == 0) {
|
||||||
|
delete translated[id];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return { [locale]: translated };
|
||||||
})
|
})
|
||||||
.reduce((collection, descriptors) => {
|
.reduce((collection, descriptors) => {
|
||||||
return { ...collection, ...descriptors };
|
return { ...collection, ...descriptors };
|
||||||
|
|||||||
+15
-1
@@ -10,7 +10,7 @@
|
|||||||
"label_default_access_mode": "Default access mode:",
|
"label_default_access_mode": "Default access mode:",
|
||||||
"label_message_sound": "Message sound:",
|
"label_message_sound": "Message sound:",
|
||||||
"label_push_notifications": "Notification alerts:",
|
"label_push_notifications": "Notification alerts:",
|
||||||
"label_push_notifications_disabled": "Notification alerts (requires HTTPS)",
|
"label_push_notifications_disabled": "Notification alerts (requires HTTPS):",
|
||||||
"title_tag_manager": "Tags (user discovery)",
|
"title_tag_manager": "Tags (user discovery)",
|
||||||
"button_logout": "Logout",
|
"button_logout": "Logout",
|
||||||
"login_prompt": "Login",
|
"login_prompt": "Login",
|
||||||
@@ -28,6 +28,7 @@
|
|||||||
"tabtitle_find_user": "find",
|
"tabtitle_find_user": "find",
|
||||||
"tabtitle_new_group": "new group",
|
"tabtitle_new_group": "new group",
|
||||||
"tabtitle_group_by_id": "by id",
|
"tabtitle_group_by_id": "by id",
|
||||||
|
"search_for_contacts": "Use search to find contacts",
|
||||||
"label_server_to_use": "Server to use:",
|
"label_server_to_use": "Server to use:",
|
||||||
"label_wire_transport": "Wire transport:",
|
"label_wire_transport": "Wire transport:",
|
||||||
"button_update": "Update",
|
"button_update": "Update",
|
||||||
@@ -38,11 +39,24 @@
|
|||||||
"sidepanel_title_newtpk": "Start New Chat",
|
"sidepanel_title_newtpk": "Start New Chat",
|
||||||
"sidepanel_title_cred": "Confirm Credentials",
|
"sidepanel_title_cred": "Confirm Credentials",
|
||||||
"sidepanel_title_reset": "Reset Password",
|
"sidepanel_title_reset": "Reset Password",
|
||||||
|
"phone": "phone",
|
||||||
|
"email": "email",
|
||||||
|
"enter_confirmation_code_prompt": "Enter confirmation code sent to you by {method}:",
|
||||||
|
"numeric_confirmation_code_prompt": "Numbers only",
|
||||||
|
"button_confirm": "Confirm",
|
||||||
|
"invalid_content": "invalid content",
|
||||||
|
"button_ok": "OK",
|
||||||
|
"button_cancel": "Cancel",
|
||||||
|
"label_file_name": "File name:",
|
||||||
|
"label_content_type": "Content type:",
|
||||||
|
"label_size": "Size:",
|
||||||
"message_sending": "sending...",
|
"message_sending": "sending...",
|
||||||
"message_sending_failed": "failed",
|
"message_sending_failed": "failed",
|
||||||
"search_placeholder": "List like email:alice@example.com, tel:17025550003...",
|
"search_placeholder": "List like email:alice@example.com, tel:17025550003...",
|
||||||
"messaging_disabled_prompt": "Messaging disabled",
|
"messaging_disabled_prompt": "Messaging disabled",
|
||||||
"new_message_prompt": "New message",
|
"new_message_prompt": "New message",
|
||||||
|
"file_attachment_too_large": "The file size {size} exceeds the {limit} limit.",
|
||||||
|
"cannot_initiate_file_upload": "Cannot initiate file upload.",
|
||||||
"tags_not_found": "No tags defined. Add some.",
|
"tags_not_found": "No tags defined. Add some.",
|
||||||
"tags_editor_no_tags": "Add some tags",
|
"tags_editor_no_tags": "Add some tags",
|
||||||
"title_manage_tags": "Manage tags"
|
"title_manage_tags": "Manage tags"
|
||||||
|
|||||||
+21
-2
@@ -40,5 +40,24 @@
|
|||||||
"sidepanel_title_reset": "Сменить пароль",
|
"sidepanel_title_reset": "Сменить пароль",
|
||||||
"tags_not_found": "Тагов нет. Добавьте",
|
"tags_not_found": "Тагов нет. Добавьте",
|
||||||
"tags_editor_no_tags": "Добавьте таги",
|
"tags_editor_no_tags": "Добавьте таги",
|
||||||
"title_manage_tags": "Редактор тагов"
|
"title_manage_tags": "Редактор тагов",
|
||||||
}
|
"message_sending": "шлём...",
|
||||||
|
"message_sending_failed": "ошибка",
|
||||||
|
"search_placeholder": "Список, напр. email:alice@example.com, tel:17025550003...",
|
||||||
|
"messaging_disabled_prompt": "Отправка недоступна",
|
||||||
|
"new_message_prompt": "Новое сообщение",
|
||||||
|
"file_attachment_too_large": "Размер файла {size} превышает {limit} лимит.",
|
||||||
|
"cannot_initiate_file_upload": "Ошибка загрузки файла.",
|
||||||
|
"search_for_contacts": ":!![MISSING]!!:Use search to find contacts",
|
||||||
|
"phone": "телефон",
|
||||||
|
"email": "емейл",
|
||||||
|
"enter_confirmation_code_prompt": "Введите код подтверждения {method}:",
|
||||||
|
"numeric_confirmation_code_prompt": "Только цифры",
|
||||||
|
"button_confirm": "Подтвердить",
|
||||||
|
"button_ok": "OK",
|
||||||
|
"button_cancel": "Отменить",
|
||||||
|
"invalid_content": "сообщение не читается",
|
||||||
|
"label_file_name": "Имя файла:",
|
||||||
|
"label_content_type": "Тип:",
|
||||||
|
"label_size": "Размер:"
|
||||||
|
}
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
// File and image helper functions.
|
// File and image helper functions.
|
||||||
|
import { MAX_INBAND_ATTACHMENT_SIZE, MAX_IMAGE_DIM } from '../config.js';
|
||||||
|
|
||||||
// Supported image MIME types and corresponding file extensions.
|
// Supported image MIME types and corresponding file extensions.
|
||||||
export const SUPPORTED_IMAGE_FORMATS = ['image/jpeg', 'image/gif', 'image/png', 'image/svg', 'image/svg+xml'];
|
export const SUPPORTED_IMAGE_FORMATS = ['image/jpeg', 'image/gif', 'image/png', 'image/svg', 'image/svg+xml'];
|
||||||
@@ -204,7 +205,7 @@ export function base64ReEncode(str) {
|
|||||||
try {
|
try {
|
||||||
str = btoa(atob(str));
|
str = btoa(atob(str));
|
||||||
} catch(err) {
|
} catch(err) {
|
||||||
console.log("Failed to base64 re-decode string");
|
console.log("Failed to base64 re-encode string");
|
||||||
str = null;
|
str = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+34
-2
@@ -11,7 +11,7 @@
|
|||||||
"label_default_access_mode": "Default access mode:",
|
"label_default_access_mode": "Default access mode:",
|
||||||
"label_message_sound": "Message sound:",
|
"label_message_sound": "Message sound:",
|
||||||
"label_push_notifications": "Notification alerts:",
|
"label_push_notifications": "Notification alerts:",
|
||||||
"label_push_notifications_disabled": "Notification alerts (requires HTTPS)",
|
"label_push_notifications_disabled": "Notification alerts (requires HTTPS):",
|
||||||
"title_tag_manager": "Tags (user discovery)",
|
"title_tag_manager": "Tags (user discovery)",
|
||||||
"button_logout": "Logout",
|
"button_logout": "Logout",
|
||||||
"login_prompt": "Login",
|
"login_prompt": "Login",
|
||||||
@@ -29,6 +29,7 @@
|
|||||||
"tabtitle_find_user": "find",
|
"tabtitle_find_user": "find",
|
||||||
"tabtitle_new_group": "new group",
|
"tabtitle_new_group": "new group",
|
||||||
"tabtitle_group_by_id": "by id",
|
"tabtitle_group_by_id": "by id",
|
||||||
|
"search_for_contacts": "Use search to find contacts",
|
||||||
"label_server_to_use": "Server to use:",
|
"label_server_to_use": "Server to use:",
|
||||||
"label_wire_transport": "Wire transport:",
|
"label_wire_transport": "Wire transport:",
|
||||||
"button_update": "Update",
|
"button_update": "Update",
|
||||||
@@ -39,11 +40,24 @@
|
|||||||
"sidepanel_title_newtpk": "Start New Chat",
|
"sidepanel_title_newtpk": "Start New Chat",
|
||||||
"sidepanel_title_cred": "Confirm Credentials",
|
"sidepanel_title_cred": "Confirm Credentials",
|
||||||
"sidepanel_title_reset": "Reset Password",
|
"sidepanel_title_reset": "Reset Password",
|
||||||
|
"phone": "phone",
|
||||||
|
"email": "email",
|
||||||
|
"enter_confirmation_code_prompt": "Enter confirmation code sent to you by {method}:",
|
||||||
|
"numeric_confirmation_code_prompt": "Numbers only",
|
||||||
|
"button_confirm": "Confirm",
|
||||||
|
"invalid_content": "invalid content",
|
||||||
|
"button_ok": "OK",
|
||||||
|
"button_cancel": "Cancel",
|
||||||
|
"label_file_name": "File name:",
|
||||||
|
"label_content_type": "Content type:",
|
||||||
|
"label_size": "Size:",
|
||||||
"message_sending": "sending...",
|
"message_sending": "sending...",
|
||||||
"message_sending_failed": "failed",
|
"message_sending_failed": "failed",
|
||||||
"search_placeholder": "List like email:alice@example.com, tel:17025550003...",
|
"search_placeholder": "List like email:alice@example.com, tel:17025550003...",
|
||||||
"messaging_disabled_prompt": "Messaging disabled",
|
"messaging_disabled_prompt": "Messaging disabled",
|
||||||
"new_message_prompt": "New message",
|
"new_message_prompt": "New message",
|
||||||
|
"file_attachment_too_large": "The file size {size} exceeds the {limit} limit.",
|
||||||
|
"cannot_initiate_file_upload": "Cannot initiate file upload.",
|
||||||
"tags_not_found": "No tags defined. Add some.",
|
"tags_not_found": "No tags defined. Add some.",
|
||||||
"tags_editor_no_tags": "Add some tags",
|
"tags_editor_no_tags": "Add some tags",
|
||||||
"title_manage_tags": "Manage tags"
|
"title_manage_tags": "Manage tags"
|
||||||
@@ -90,6 +104,24 @@
|
|||||||
"sidepanel_title_reset": "Сменить пароль",
|
"sidepanel_title_reset": "Сменить пароль",
|
||||||
"tags_not_found": "Тагов нет. Добавьте",
|
"tags_not_found": "Тагов нет. Добавьте",
|
||||||
"tags_editor_no_tags": "Добавьте таги",
|
"tags_editor_no_tags": "Добавьте таги",
|
||||||
"title_manage_tags": "Редактор тагов"
|
"title_manage_tags": "Редактор тагов",
|
||||||
|
"message_sending": "шлём...",
|
||||||
|
"message_sending_failed": "ошибка",
|
||||||
|
"search_placeholder": "Список, напр. email:alice@example.com, tel:17025550003...",
|
||||||
|
"messaging_disabled_prompt": "Отправка недоступна",
|
||||||
|
"new_message_prompt": "Новое сообщение",
|
||||||
|
"file_attachment_too_large": "Размер файла {size} превышает {limit} лимит.",
|
||||||
|
"cannot_initiate_file_upload": "Ошибка загрузки файла.",
|
||||||
|
"phone": "телефон",
|
||||||
|
"email": "емейл",
|
||||||
|
"enter_confirmation_code_prompt": "Введите код подтверждения {method}:",
|
||||||
|
"numeric_confirmation_code_prompt": "Только цифры",
|
||||||
|
"button_confirm": "Подтвердить",
|
||||||
|
"button_ok": "OK",
|
||||||
|
"button_cancel": "Отменить",
|
||||||
|
"invalid_content": "сообщение не читается",
|
||||||
|
"label_file_name": "Имя файла:",
|
||||||
|
"label_content_type": "Тип:",
|
||||||
|
"label_size": "Размер:"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -196,8 +196,8 @@ export default class EditAccountView extends React.Component {
|
|||||||
description="Label for push notifications switch" />
|
description="Label for push notifications switch" />
|
||||||
:
|
:
|
||||||
<FormattedMessage id="label_push_notifications_disabled"
|
<FormattedMessage id="label_push_notifications_disabled"
|
||||||
defaultMessage="Notification alerts (requires HTTPS)"
|
defaultMessage="Notification alerts (requires HTTPS):"
|
||||||
description="Label for push notifications switch (disabled):" />
|
description="Label for push notifications switch (disabled)" />
|
||||||
}
|
}
|
||||||
</label>
|
</label>
|
||||||
<CheckBox name="alert" id="desktop-alerts"
|
<CheckBox name="alert" id="desktop-alerts"
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
/* InfoView: panel with topic/user info */
|
// InfoView: panel with topic/user info.
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import AvatarUpload from '../widgets/avatar-upload.jsx';
|
import AvatarUpload from '../widgets/avatar-upload.jsx';
|
||||||
|
import CheckBox from '../widgets/checkbox.jsx';
|
||||||
import ContactList from '../widgets/contact-list.jsx';
|
import ContactList from '../widgets/contact-list.jsx';
|
||||||
import GroupManager from '../widgets/group-manager.jsx';
|
import GroupManager from '../widgets/group-manager.jsx';
|
||||||
import InPlaceEdit from '../widgets/in-place-edit.jsx';
|
import InPlaceEdit from '../widgets/in-place-edit.jsx';
|
||||||
@@ -11,6 +11,8 @@ import MoreButton from '../widgets/more-button.jsx';
|
|||||||
import PermissionsEditor from '../widgets/permissions-editor.jsx';
|
import PermissionsEditor from '../widgets/permissions-editor.jsx';
|
||||||
import TagManager from '../widgets/tag-manager.jsx';
|
import TagManager from '../widgets/tag-manager.jsx';
|
||||||
|
|
||||||
|
import { makeImageUrl } from '../lib/blob-helpers.js';
|
||||||
|
|
||||||
export default class InfoView extends React.Component {
|
export default class InfoView extends React.Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
|
|||||||
@@ -90,15 +90,18 @@ export default class NewTopicView extends React.Component {
|
|||||||
<div className="flex-column">
|
<div className="flex-column">
|
||||||
<SearchContacts type="p2p"
|
<SearchContacts type="p2p"
|
||||||
onSearchContacts={this.props.onSearchContacts} />
|
onSearchContacts={this.props.onSearchContacts} />
|
||||||
<ContactList
|
<FormattedMessage id="search_for_contacts" defaultMessage="Use search to find contacts"
|
||||||
contacts={this.state.contactList}
|
description="Prompt in search field.">{
|
||||||
myUserId={this.props.myUserId}
|
(search_for_contacts) => <ContactList
|
||||||
emptyListMessage="Use search to find contacts"
|
contacts={this.state.contactList}
|
||||||
topicSelected={this.state.selectedContact}
|
myUserId={this.props.myUserId}
|
||||||
showOnline={false}
|
emptyListMessage={search_for_contacts}
|
||||||
showUnread={false}
|
topicSelected={this.state.selectedContact}
|
||||||
showContextMenu={false}
|
showOnline={false}
|
||||||
onTopicSelected={this.handleContactSelected} />
|
showUnread={false}
|
||||||
|
showContextMenu={false}
|
||||||
|
onTopicSelected={this.handleContactSelected} />
|
||||||
|
}</FormattedMessage>
|
||||||
</div>}
|
</div>}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
/* A password reset form */
|
// A password reset form.
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import VisiblePassword from '../widgets/visible-password.jsx';
|
import VisiblePassword from '../widgets/visible-password.jsx';
|
||||||
|
|||||||
@@ -1,7 +1,21 @@
|
|||||||
// ValidationView: panel for confirming credentials, like email or phone.
|
// ValidationView: panel for confirming credentials, like email or phone.
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import { FormattedMessage, defineMessages, injectIntl } from 'react-intl';
|
||||||
|
|
||||||
export default class ValidationView extends React.PureComponent {
|
const messages = defineMessages({
|
||||||
|
phone: {
|
||||||
|
id: 'phone',
|
||||||
|
defaultMessage: 'phone',
|
||||||
|
description: "The word 'phone'",
|
||||||
|
},
|
||||||
|
email: {
|
||||||
|
id: 'email',
|
||||||
|
defaultMessage: 'email',
|
||||||
|
description: "The word 'email'",
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
class ValidationView extends React.PureComponent {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
@@ -32,24 +46,35 @@ export default class ValidationView extends React.PureComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
var methods = {'email': "email", 'tel': "phone"};
|
const { formatMessage } = this.props.intl;
|
||||||
var method = methods[this.props.credMethod] || this.props.credMethod;
|
const methods = {'email': formatMessage(messages.email), 'tel': formatMessage(messages.phone)};
|
||||||
|
let method = methods[this.props.credMethod] || this.props.credMethod;
|
||||||
return (
|
return (
|
||||||
<div className="panel-form">
|
<div className="panel-form">
|
||||||
<div className="panel-form-row">
|
<div className="panel-form-row">
|
||||||
<label className="small" htmlFor="enter-confirmation-code">
|
<label className="small" htmlFor="enter-confirmation-code">
|
||||||
Enter confirmation code sent to you by {method}:
|
<FormattedMessage id="enter_confirmation_code_prompt"
|
||||||
|
defaultMessage="Enter confirmation code sent to you by {method}:"
|
||||||
|
description="Request to enter confirmation code" />
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<div className="panel-form-row">
|
<div className="panel-form-row">
|
||||||
<input type="text" id="enter-confirmation-code" placeholder="Numbers only"
|
<FormattedMessage id="numeric_confirmation_code_prompt"
|
||||||
value={this.state.code} onChange={this.handleChange}
|
defaultMessage="Numbers only" description="Prompt for numeric conformation code">{
|
||||||
onKeyPress={this.handleKeyPress} required />
|
(numbers_only) => <input type="text" id="enter-confirmation-code"
|
||||||
|
placeholder={numbers_only}
|
||||||
|
value={this.state.code} onChange={this.handleChange}
|
||||||
|
onKeyPress={this.handleKeyPress} required />
|
||||||
|
}</FormattedMessage>
|
||||||
</div>
|
</div>
|
||||||
<div className="dialog-buttons">
|
<div className="dialog-buttons">
|
||||||
<button className="blue" onClick={this.handleSubmit}>Confirm</button>
|
<button className="blue" onClick={this.handleSubmit}>
|
||||||
|
<FormattedMessage id="button_confirm" defaultMessage="Confirm" description="Button [Confirm]" />
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export default injectIntl(ValidationView);
|
||||||
|
|||||||
@@ -1,11 +1,15 @@
|
|||||||
|
// Single message, sent or received.
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import {Drafty} from 'tinode-sdk'
|
import { FormattedMessage } from 'react-intl';
|
||||||
|
import { Drafty } from 'tinode-sdk'
|
||||||
|
|
||||||
import Attachment from './attachment.jsx';
|
import Attachment from './attachment.jsx';
|
||||||
import LetterTile from './letter-tile.jsx';
|
import LetterTile from './letter-tile.jsx';
|
||||||
import ReceivedMarker from './received-marker.jsx'
|
import ReceivedMarker from './received-marker.jsx'
|
||||||
|
|
||||||
/* Single message, sent or received. */
|
import { REM_SIZE } from '../config.js';
|
||||||
|
import { fitImageSize } from '../lib/blob-helpers.js';
|
||||||
|
|
||||||
export default class ChatMessage extends React.Component {
|
export default class ChatMessage extends React.Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
@@ -50,17 +54,16 @@ export default class ChatMessage extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
var elementKey = 0;
|
let elementKey = 0;
|
||||||
|
const formatter = function(style, data, values) {
|
||||||
var formatter = function(style, data, values) {
|
|
||||||
elementKey += 1;
|
elementKey += 1;
|
||||||
var el = Drafty.tagName(style);
|
let el = Drafty.tagName(style);
|
||||||
if (el) {
|
if (el) {
|
||||||
var attr = Drafty.attrValue(style, data) || {};
|
let attr = Drafty.attrValue(style, data) || {};
|
||||||
attr.key = elementKey;
|
attr.key = elementKey;
|
||||||
if (style == 'IM') {
|
if (style == 'IM') {
|
||||||
// Additional processing for images
|
// Additional processing for images
|
||||||
var dim = fitImageSize(data.width, data.height,
|
let dim = fitImageSize(data.width, data.height,
|
||||||
Math.min(this.props.viewportWidth - REM_SIZE * 4, REM_SIZE * 36), REM_SIZE * 24, false);
|
Math.min(this.props.viewportWidth - REM_SIZE * 4, REM_SIZE * 36), REM_SIZE * 24, false);
|
||||||
attr.className = 'inline-image';
|
attr.className = 'inline-image';
|
||||||
attr.style = dim ? { width: dim.dstWidth + 'px', height: dim.dstHeight + 'px' } : null;
|
attr.style = dim ? { width: dim.dstWidth + 'px', height: dim.dstHeight + 'px' } : null;
|
||||||
@@ -95,7 +98,10 @@ export default class ChatMessage extends React.Component {
|
|||||||
}, this);
|
}, this);
|
||||||
content = React.createElement('span', null, Drafty.format(content, formatter, this));
|
content = React.createElement('span', null, Drafty.format(content, formatter, this));
|
||||||
} else if (typeof content != 'string') {
|
} else if (typeof content != 'string') {
|
||||||
content = <span><i className="material-icons">error_outline</i> <i>invalid content</i></span>
|
content = <span><i className="material-icons">error_outline</i> <i>
|
||||||
|
<FormattedMessage id="invalid_content"
|
||||||
|
defaultMessage="invalid content" description="Shown when message is unreadable" />
|
||||||
|
</i></span>
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
+10
-10
@@ -1,10 +1,10 @@
|
|||||||
|
// A single topic or user.
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import { FormattedMessage } from 'react-intl';
|
||||||
|
|
||||||
import LetterTile from './letter-tile.jsx';
|
import LetterTile from './letter-tile.jsx';
|
||||||
import UnreadBadge from './unread-badge.jsx';
|
import UnreadBadge from './unread-badge.jsx';
|
||||||
|
|
||||||
/* A single topic or user */
|
|
||||||
|
|
||||||
export default class Contact extends React.Component {
|
export default class Contact extends React.Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
@@ -28,20 +28,20 @@ export default class Contact extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
var title = this.props.title;
|
let title = this.props.title;
|
||||||
if (!title) {
|
if (!title) {
|
||||||
title = <i>unknown</i>;
|
title = <i><FormattedMessage id="unnamed_topic" /></i>;
|
||||||
} else if (title.length > 30) {
|
} else if (title.length > 30) {
|
||||||
// FIXME: this is wrong for RTL languages.
|
// FIXME: this is probably wrong for RTL languages.
|
||||||
title = title.substring(0, 28) + '...';
|
title = title.substring(0, 28) + '...';
|
||||||
}
|
}
|
||||||
var online = this.props.now ? 'online' : 'offline';
|
let online = this.props.now ? 'online' : 'offline';
|
||||||
var avatar = this.props.avatar ? this.props.avatar : true;
|
let avatar = this.props.avatar ? this.props.avatar : true;
|
||||||
var badges = [];
|
let badges = [];
|
||||||
if (this.props.badges && this.props.badges.length > 0) {
|
if (this.props.badges && this.props.badges.length > 0) {
|
||||||
var count = 0;
|
let count = 0;
|
||||||
this.props.badges.map(function(b) {
|
this.props.badges.map(function(b) {
|
||||||
var style = 'badge' + (b.color ? ' ' + b.color : '');
|
let style = 'badge' + (b.color ? ' ' + b.color : '');
|
||||||
badges.push(<span className={style} key={count}>{b.name}</span>);
|
badges.push(<span className={style} key={count}>{b.name}</span>);
|
||||||
count ++;
|
count ++;
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
// Context Menu: popup/dropdown menu.
|
// Context Menu: popup/dropdown menu.
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import ReactDom from 'react-dom';
|
import ReactDOM from 'react-dom';
|
||||||
|
|
||||||
import { REM_SIZE } from '../config.js';
|
import { REM_SIZE } from '../config.js';
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/* GroupMembers: control for managing a list of group members */
|
// GroupMembers: control for managing a list of group members.
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import { FormattedMessage } from 'react-intl';
|
||||||
|
|
||||||
import ChipInput from './chip-input.jsx';
|
import ChipInput from './chip-input.jsx';
|
||||||
import ContactList from './contact-list.jsx';
|
import ContactList from './contact-list.jsx';
|
||||||
@@ -156,8 +156,12 @@ export default class GroupManager extends React.Component {
|
|||||||
showUnread={false}
|
showUnread={false}
|
||||||
onTopicSelected={this.handleContactSelected} />
|
onTopicSelected={this.handleContactSelected} />
|
||||||
<div id="group-manager-buttons" className="panel-form-row">
|
<div id="group-manager-buttons" className="panel-form-row">
|
||||||
<button className="blue" onClick={this.handleSubmit}>OK</button>
|
<button className="blue" onClick={this.handleSubmit}>
|
||||||
<button className="white" onClick={this.handleCancel}>Cancel</button>
|
<FormattedMessage id="button_ok" defaultMessage="OK" description="Button [OK]" />
|
||||||
|
</button>
|
||||||
|
<button className="white" onClick={this.handleCancel}>
|
||||||
|
<FormattedMessage id="button_cancel" defaultMessage="Cancel" description="Button [Cancel]" />
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,4 +1,9 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import { FormattedMessage } from 'react-intl';
|
||||||
|
|
||||||
|
import { REM_SIZE } from '../config.js';
|
||||||
|
import { fitImageSize } from '../lib/blob-helpers.js';
|
||||||
|
import { bytesToHumanSize } from '../lib/strformat.js';
|
||||||
|
|
||||||
export default class ImagePreview extends React.PureComponent {
|
export default class ImagePreview extends React.PureComponent {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
@@ -43,10 +48,19 @@ export default class ImagePreview extends React.PureComponent {
|
|||||||
<img src={this.props.content.url} style={size} />
|
<img src={this.props.content.url} style={size} />
|
||||||
</div>
|
</div>
|
||||||
<div id="image-preview-footer">
|
<div id="image-preview-footer">
|
||||||
<div><div><b>File name</b>:</div><div><span title={this.props.content.filename}>{filename}</span></div></div>
|
|
||||||
<div><div><b>Content type</b>:</div><div>{this.props.content.type}</div></div>
|
|
||||||
<div>
|
<div>
|
||||||
<div><b>Size</b>:</div>
|
<div><b><FormattedMessage id="label_file_name" defaultMessage="File name:"
|
||||||
|
description="Label for a file name" /></b></div>
|
||||||
|
<div><span title={this.props.content.filename}>{filename}</span></div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div><b><FormattedMessage id="label_content_type" defaultMessage="Content type:"
|
||||||
|
description="Label for file content type (mime)" /></b></div>
|
||||||
|
<div>{this.props.content.type}</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div><b><FormattedMessage id="label_size" defaultMessage="Size:"
|
||||||
|
description="Label for file size" /></b></div>
|
||||||
<div>{this.props.content.width} × {this.props.content.height} px; {bytesToHumanSize(this.props.content.size)}</div>
|
<div>{this.props.content.width} × {this.props.content.height} px; {bytesToHumanSize(this.props.content.size)}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
// In-place text editor. Shows text with an icon which toggles it into an input field.
|
// In-place text editor. Shows text with an icon which toggles it into an input field.
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import ReactDom from 'react-dom';
|
import ReactDOM from 'react-dom';
|
||||||
|
|
||||||
import VisiblePassword from './visible-password.jsx';
|
import VisiblePassword from './visible-password.jsx';
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,11 @@
|
|||||||
// Send message form.
|
// Send message form.
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { defineMessages, injectIntl } from 'react-intl';
|
import { defineMessages, injectIntl } from 'react-intl';
|
||||||
|
import { Drafty } from 'tinode-sdk';
|
||||||
|
|
||||||
import { KEYPRESS_DELAY } from '../config.js';
|
import { KEYPRESS_DELAY, MAX_EXTERN_ATTACHMENT_SIZE, MAX_IMAGE_DIM, MAX_INBAND_ATTACHMENT_SIZE } from '../config.js';
|
||||||
|
import { SUPPORTED_IMAGE_FORMATS, filePasted, imageFileToBase64, imageFileScaledToBase64 } from '../lib/blob-helpers.js';
|
||||||
|
import { bytesToHumanSize } from '../lib/strformat.js';
|
||||||
|
|
||||||
const messages = defineMessages({
|
const messages = defineMessages({
|
||||||
'messaging_disabled': {
|
'messaging_disabled': {
|
||||||
@@ -14,7 +17,17 @@ const messages = defineMessages({
|
|||||||
id: 'new_message_prompt',
|
id: 'new_message_prompt',
|
||||||
defaultMessage: 'New message',
|
defaultMessage: 'New message',
|
||||||
description: 'Prompt in SendMessage in read-only topic'
|
description: 'Prompt in SendMessage in read-only topic'
|
||||||
}
|
},
|
||||||
|
'file_attachment_too_large': {
|
||||||
|
id: 'file_attachment_too_large',
|
||||||
|
defaultMessage: 'The file size {size} exceeds the {limit} limit.',
|
||||||
|
description: 'Error message when attachment is too large'
|
||||||
|
},
|
||||||
|
'cannot_initiate_upload': {
|
||||||
|
id: 'cannot_initiate_file_upload',
|
||||||
|
defaultMessage: 'Cannot initiate file upload.',
|
||||||
|
description: 'Generic error messagewhen attachment fails'
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
class SendMessage extends React.PureComponent {
|
class SendMessage extends React.PureComponent {
|
||||||
@@ -99,17 +112,18 @@ class SendMessage extends React.PureComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
handleAttachFile(e) {
|
handleAttachFile(e) {
|
||||||
|
const {formatMessage} = this.props.intl;
|
||||||
if (e.target.files && e.target.files.length > 0) {
|
if (e.target.files && e.target.files.length > 0) {
|
||||||
var file = e.target.files[0];
|
var file = e.target.files[0];
|
||||||
if (file.size > MAX_EXTERN_ATTACHMENT_SIZE) {
|
if (file.size > MAX_EXTERN_ATTACHMENT_SIZE) {
|
||||||
// Too large.
|
// Too large.
|
||||||
this.props.onError("The file size " + bytesToHumanSize(file.size) +
|
this.props.onError(formatMessage(messages.file_attachment_too_large,
|
||||||
" exceeds the " + bytesToHumanSize(MAX_EXTERN_ATTACHMENT_SIZE) + " limit.", 'err');
|
{size: bytesToHumanSize(file.size), limit: bytesToHumanSize(MAX_EXTERN_ATTACHMENT_SIZE)}), 'err');
|
||||||
} else if (file.size > MAX_INBAND_ATTACHMENT_SIZE) {
|
} else if (file.size > MAX_INBAND_ATTACHMENT_SIZE) {
|
||||||
// Too large to send inband - uploading out of band and sending as a link.
|
// Too large to send inband - uploading out of band and sending as a link.
|
||||||
let uploader = this.props.tinode.getLargeFileHelper();
|
let uploader = this.props.tinode.getLargeFileHelper();
|
||||||
if (!uploader) {
|
if (!uploader) {
|
||||||
this.props.onError("Cannot initiate file upload.");
|
this.props.onError(formatMessage(messages.cannot_initiate_upload));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// Format data and initiate upload.
|
// Format data and initiate upload.
|
||||||
|
|||||||
+13
-13
File diff suppressed because one or more lines are too long
+6
-6
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user