more corrections and translations
This commit is contained in:
@@ -21,9 +21,12 @@ Background pattern from http://subtlepatterns.com/, commercial and non-commercia
|
|||||||
* For support, general questions, discussions post to [https://groups.google.com/d/forum/tinode](https://groups.google.com/d/forum/tinode).
|
* For support, general questions, discussions post to [https://groups.google.com/d/forum/tinode](https://groups.google.com/d/forum/tinode).
|
||||||
* For bugs and feature requests [open an issue](https://github.com/tinode/webchat/issues/new).
|
* For bugs and feature requests [open an issue](https://github.com/tinode/webchat/issues/new).
|
||||||
|
|
||||||
## Translations
|
## Internationalization
|
||||||
|
|
||||||
|
The app is fully internationalized using [React-Intl](https://github.com/yahoo/react-intl). The UI language is selected automatically from the language specified by the browser. A specific language can be forced by adding `hl=XX` parameter to the URL, i.e. https://api.tinode.co/#?hl=ru.
|
||||||
|
|
||||||
|
More translations are welcome. Send a pull request with a json file with translated strings. As an example, take a look at [English](/src/i18n/en.json) and [Russian](/src/i18n/ru.json) translations.
|
||||||
|
|
||||||
The app is fully internationalized using [React-Intl](https://github.com/yahoo/react-intl). You can add a translation for your favorite language by sending a pull request with a json file with translated strings. As an example, take a look at [English](/src/i18n/en.json) and [Russian](/src/i18n/ru.json) translations.
|
|
||||||
|
|
||||||
## Missing functionality
|
## Missing functionality
|
||||||
|
|
||||||
|
|||||||
Generated
+457
-894
File diff suppressed because it is too large
Load Diff
+1
-1
@@ -10,7 +10,7 @@
|
|||||||
"babel-plugin-react-intl": "^3.0.1",
|
"babel-plugin-react-intl": "^3.0.1",
|
||||||
"browserslist": "^4.3.3",
|
"browserslist": "^4.3.3",
|
||||||
"cssnano": "^4.0.5",
|
"cssnano": "^4.0.5",
|
||||||
"webpack": "^4.22.0",
|
"webpack": "^4.23.1",
|
||||||
"webpack-cli": "^3.1.2"
|
"webpack-cli": "^3.1.2"
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
|
|||||||
@@ -68,8 +68,8 @@ globSync(LANG_PATTERN)
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Combine all translations into a single json {"lang_1": {...}, "lang_2: {...}"};
|
// Combine all translations into a single json {"lang_1": {...}, "lang_2: {...}"}
|
||||||
// Remove messages marked as missing or obsolete.
|
// while removing messages marked as missing or obsolete.
|
||||||
|
|
||||||
// Read all translations.
|
// Read all translations.
|
||||||
messages = globSync(LANG_PATTERN)
|
messages = globSync(LANG_PATTERN)
|
||||||
|
|||||||
@@ -45,11 +45,22 @@
|
|||||||
"numeric_confirmation_code_prompt": "Numbers only",
|
"numeric_confirmation_code_prompt": "Numbers only",
|
||||||
"button_confirm": "Confirm",
|
"button_confirm": "Confirm",
|
||||||
"invalid_content": "invalid content",
|
"invalid_content": "invalid content",
|
||||||
|
"title_group_members": "Group members",
|
||||||
"button_ok": "OK",
|
"button_ok": "OK",
|
||||||
"button_cancel": "Cancel",
|
"button_cancel": "Cancel",
|
||||||
|
"download_action": "download",
|
||||||
"label_file_name": "File name:",
|
"label_file_name": "File name:",
|
||||||
"label_content_type": "Content type:",
|
"label_content_type": "Content type:",
|
||||||
"label_size": "Size:",
|
"label_size": "Size:",
|
||||||
|
"permission_join": "Join ({val})",
|
||||||
|
"permission_read": "Read ({val})",
|
||||||
|
"permission_write": "Write ({val})",
|
||||||
|
"permission_pres": "Get notified ({val})",
|
||||||
|
"permission_admin": "Approve ({val})",
|
||||||
|
"permission_share": "Share ({val})",
|
||||||
|
"permission_delete": "Delete ({val})",
|
||||||
|
"permission_owner": "Owner ({val})",
|
||||||
|
"title_permissions": "Permissions",
|
||||||
"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...",
|
||||||
|
|||||||
+13
-2
@@ -41,7 +41,7 @@
|
|||||||
"tags_not_found": "Тагов нет. Добавьте",
|
"tags_not_found": "Тагов нет. Добавьте",
|
||||||
"tags_editor_no_tags": "Добавьте таги",
|
"tags_editor_no_tags": "Добавьте таги",
|
||||||
"title_manage_tags": "Редактор тагов",
|
"title_manage_tags": "Редактор тагов",
|
||||||
"message_sending": "шлём...",
|
"message_sending": "в пути...",
|
||||||
"message_sending_failed": "ошибка",
|
"message_sending_failed": "ошибка",
|
||||||
"search_placeholder": "Список, напр. email:alice@example.com, tel:17025550003...",
|
"search_placeholder": "Список, напр. email:alice@example.com, tel:17025550003...",
|
||||||
"messaging_disabled_prompt": "Отправка недоступна",
|
"messaging_disabled_prompt": "Отправка недоступна",
|
||||||
@@ -59,5 +59,16 @@
|
|||||||
"label_content_type": "Тип:",
|
"label_content_type": "Тип:",
|
||||||
"label_size": "Размер:",
|
"label_size": "Размер:",
|
||||||
"phone_dative": "телефону",
|
"phone_dative": "телефону",
|
||||||
"email_dative": "емейлу"
|
"email_dative": "емейлу",
|
||||||
|
"title_group_members": "Участники",
|
||||||
|
"download_action": "скачать",
|
||||||
|
"permission_join": "Присоединяться ({val})",
|
||||||
|
"permission_read": "Читать ({val})",
|
||||||
|
"permission_write": "Писать ({val})",
|
||||||
|
"permission_pres": "Получать уведомления ({val})",
|
||||||
|
"permission_admin": "Администрировать ({val})",
|
||||||
|
"permission_share": "Приглашать ({val})",
|
||||||
|
"permission_delete": "Удалять ({val})",
|
||||||
|
"permission_owner": "Владелец ({val})",
|
||||||
|
"title_permissions": "Права доступа"
|
||||||
}
|
}
|
||||||
+24
-2
@@ -46,11 +46,22 @@
|
|||||||
"numeric_confirmation_code_prompt": "Numbers only",
|
"numeric_confirmation_code_prompt": "Numbers only",
|
||||||
"button_confirm": "Confirm",
|
"button_confirm": "Confirm",
|
||||||
"invalid_content": "invalid content",
|
"invalid_content": "invalid content",
|
||||||
|
"title_group_members": "Group members",
|
||||||
"button_ok": "OK",
|
"button_ok": "OK",
|
||||||
"button_cancel": "Cancel",
|
"button_cancel": "Cancel",
|
||||||
|
"download_action": "download",
|
||||||
"label_file_name": "File name:",
|
"label_file_name": "File name:",
|
||||||
"label_content_type": "Content type:",
|
"label_content_type": "Content type:",
|
||||||
"label_size": "Size:",
|
"label_size": "Size:",
|
||||||
|
"permission_join": "Join ({val})",
|
||||||
|
"permission_read": "Read ({val})",
|
||||||
|
"permission_write": "Write ({val})",
|
||||||
|
"permission_pres": "Get notified ({val})",
|
||||||
|
"permission_admin": "Approve ({val})",
|
||||||
|
"permission_share": "Share ({val})",
|
||||||
|
"permission_delete": "Delete ({val})",
|
||||||
|
"permission_owner": "Owner ({val})",
|
||||||
|
"title_permissions": "Permissions",
|
||||||
"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...",
|
||||||
@@ -105,7 +116,7 @@
|
|||||||
"tags_not_found": "Тагов нет. Добавьте",
|
"tags_not_found": "Тагов нет. Добавьте",
|
||||||
"tags_editor_no_tags": "Добавьте таги",
|
"tags_editor_no_tags": "Добавьте таги",
|
||||||
"title_manage_tags": "Редактор тагов",
|
"title_manage_tags": "Редактор тагов",
|
||||||
"message_sending": "шлём...",
|
"message_sending": "в пути...",
|
||||||
"message_sending_failed": "ошибка",
|
"message_sending_failed": "ошибка",
|
||||||
"search_placeholder": "Список, напр. email:alice@example.com, tel:17025550003...",
|
"search_placeholder": "Список, напр. email:alice@example.com, tel:17025550003...",
|
||||||
"messaging_disabled_prompt": "Отправка недоступна",
|
"messaging_disabled_prompt": "Отправка недоступна",
|
||||||
@@ -123,6 +134,17 @@
|
|||||||
"label_content_type": "Тип:",
|
"label_content_type": "Тип:",
|
||||||
"label_size": "Размер:",
|
"label_size": "Размер:",
|
||||||
"phone_dative": "телефону",
|
"phone_dative": "телефону",
|
||||||
"email_dative": "емейлу"
|
"email_dative": "емейлу",
|
||||||
|
"title_group_members": "Участники",
|
||||||
|
"download_action": "скачать",
|
||||||
|
"permission_join": "Присоединяться ({val})",
|
||||||
|
"permission_read": "Читать ({val})",
|
||||||
|
"permission_write": "Писать ({val})",
|
||||||
|
"permission_pres": "Получать уведомления ({val})",
|
||||||
|
"permission_admin": "Администрировать ({val})",
|
||||||
|
"permission_share": "Приглашать ({val})",
|
||||||
|
"permission_delete": "Удалять ({val})",
|
||||||
|
"permission_owner": "Владелец ({val})",
|
||||||
|
"title_permissions": "Права доступа"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2,6 +2,9 @@ import React from 'react';
|
|||||||
|
|
||||||
import LetterTile from './letter-tile.jsx';
|
import LetterTile from './letter-tile.jsx';
|
||||||
|
|
||||||
|
import { AVATAR_SIZE } from '../config.js';
|
||||||
|
import { imageFileScaledToBase64, makeImageUrl } from '../lib/blob-helpers.js';
|
||||||
|
|
||||||
export default class AvatarUpload extends React.Component {
|
export default class AvatarUpload extends React.Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
@@ -54,7 +57,7 @@ export default class AvatarUpload extends React.Component {
|
|||||||
title={this.props.title} />
|
title={this.props.title} />
|
||||||
</div>
|
</div>
|
||||||
:
|
:
|
||||||
<div className="blank">128×128</div>}
|
<div className="blank">{AVATAR_SIZE}×{AVATAR_SIZE}</div>}
|
||||||
{this.props.readOnly ? null :
|
{this.props.readOnly ? null :
|
||||||
<input type="file" id={randId} className="inputfile hidden"
|
<input type="file" id={randId} className="inputfile hidden"
|
||||||
accept="image/*" onChange={this.handleFileUpload} />}
|
accept="image/*" onChange={this.handleFileUpload} />}
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ import React from 'react';
|
|||||||
|
|
||||||
import Chip from './chip.jsx';
|
import Chip from './chip.jsx';
|
||||||
|
|
||||||
|
import { makeImageUrl } from '../lib/blob-helpers.js';
|
||||||
|
|
||||||
/* BEGIN ChipInput: group membership widget */
|
/* BEGIN ChipInput: group membership widget */
|
||||||
export default class ChipInput extends React.Component {
|
export default class ChipInput extends React.Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
// 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 { FormattedMessage, defineMessages, injectIntl } 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';
|
||||||
|
|
||||||
export default class GroupManager extends React.Component {
|
class GroupManager extends React.Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
@@ -25,7 +25,7 @@ export default class GroupManager extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static indexMembers(members) {
|
static indexMembers(members) {
|
||||||
var index = {};
|
let index = {};
|
||||||
members.map(function(m) {
|
members.map(function(m) {
|
||||||
index[m.user] = {delta: 0, present: true}; // Delta: 0 unchanged, +1 added, -1 removed
|
index[m.user] = {delta: 0, present: true}; // Delta: 0 unchanged, +1 added, -1 removed
|
||||||
});
|
});
|
||||||
@@ -33,7 +33,7 @@ export default class GroupManager extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static selectedContacts(members) {
|
static selectedContacts(members) {
|
||||||
var sel = [];
|
let sel = [];
|
||||||
members.map(function(m) {
|
members.map(function(m) {
|
||||||
sel.push(m.user);
|
sel.push(m.user);
|
||||||
});
|
});
|
||||||
@@ -41,7 +41,7 @@ export default class GroupManager extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
handleContactSelected(userId, index) {
|
handleContactSelected(userId, index) {
|
||||||
var status = this.state.index[userId];
|
let status = this.state.index[userId];
|
||||||
if (status) {
|
if (status) {
|
||||||
if (status.present) {
|
if (status.present) {
|
||||||
// Prevent duplicate members
|
// Prevent duplicate members
|
||||||
@@ -84,7 +84,8 @@ export default class GroupManager extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
handleContactFilter(val) {
|
handleContactFilter(val) {
|
||||||
var msg = !val ?
|
const {formatMessage} = this.props.intl;
|
||||||
|
const msg = !val ?
|
||||||
"You have no contacts :-(" :
|
"You have no contacts :-(" :
|
||||||
"No contacts match '" + val + "'";
|
"No contacts match '" + val + "'";
|
||||||
|
|
||||||
@@ -129,10 +130,13 @@ export default class GroupManager extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
const {formatMessage} = this.props.intl;
|
||||||
return (
|
return (
|
||||||
<div id="group-manager">
|
<div id="group-manager">
|
||||||
<div className="panel-form-row">
|
<div className="panel-form-row">
|
||||||
<label className="small">Group members</label>
|
<label className="small">
|
||||||
|
<FormattedMessage id="title_group_members" defaultMessage="Group members" description="Section title" />
|
||||||
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<div className="panel-form-row">
|
<div className="panel-form-row">
|
||||||
<ChipInput
|
<ChipInput
|
||||||
@@ -167,3 +171,5 @@ export default class GroupManager extends React.Component {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export default injectIntl(GroupManager);
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import LetterTile from './letter-tile.jsx';
|
import LetterTile from './letter-tile.jsx';
|
||||||
|
import { makeImageUrl } from '../lib/blob-helpers.js';
|
||||||
|
|
||||||
export default class GroupSubs extends React.Component {
|
export default class GroupSubs extends React.Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
|
|||||||
@@ -40,7 +40,8 @@ export default class ImagePreview extends React.PureComponent {
|
|||||||
<div id="image-preview" onClick={this.props.onClose}>
|
<div id="image-preview" onClick={this.props.onClose}>
|
||||||
<div id="image-preview-caption-panel">
|
<div id="image-preview-caption-panel">
|
||||||
<a href={this.props.content.url} download={this.props.content.filename}>
|
<a href={this.props.content.url} download={this.props.content.filename}>
|
||||||
<i className="material-icons">file_download</i> download
|
<i className="material-icons">file_download</i> <FormattedMessage
|
||||||
|
id="download_action" defaultMessage="download" description="Call to action [download]" />
|
||||||
</a>
|
</a>
|
||||||
<a href="javascript:;" onClick={this.props.onClose}><i className="material-icons gray">close</i></a>
|
<a href="javascript:;" onClick={this.props.onClose}><i className="material-icons gray">close</i></a>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
// The X menu to be displayed in title bars.
|
// The X menu to be displayed in title bars.
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
export default class MenuCancel extends React.PureComponent {
|
export default class MenuCancel extends React.PureComponent {
|
||||||
|
|||||||
@@ -1,11 +1,58 @@
|
|||||||
// PermissionsEditor: Component for editing permissions
|
// PermissionsEditor: Component for editing permissions
|
||||||
// <PermissionsEditor mode="JWROD" skip="O" onChange={this.handleCheckboxTest} />
|
// <PermissionsEditor mode="JWROD" skip="O" onChange={this.handleCheckboxTest} />
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import { FormattedMessage, defineMessages, injectIntl } from 'react-intl';
|
||||||
|
|
||||||
import CheckBox from './checkbox.jsx';
|
import CheckBox from './checkbox.jsx';
|
||||||
|
import Contact from './contact.jsx';
|
||||||
|
|
||||||
export default class PermissionsEditor extends React.Component {
|
import { makeImageUrl } from '../lib/blob-helpers.js';
|
||||||
|
|
||||||
|
// Translatable permission names.
|
||||||
|
const messages = defineMessages({
|
||||||
|
'joiner': {
|
||||||
|
id: 'permission_join',
|
||||||
|
defaultMessage: "Join ({val})",
|
||||||
|
description: 'Name of J permission'
|
||||||
|
},
|
||||||
|
'reader': {
|
||||||
|
id: 'permission_read',
|
||||||
|
defaultMessage: "Read ({val})",
|
||||||
|
description: 'Name of R permission'
|
||||||
|
},
|
||||||
|
'writer': {
|
||||||
|
id: 'permission_write',
|
||||||
|
defaultMessage: "Write ({val})",
|
||||||
|
description: 'Name of W permission'
|
||||||
|
},
|
||||||
|
'preser': {
|
||||||
|
id: 'permission_pres',
|
||||||
|
defaultMessage: "Get notified ({val})",
|
||||||
|
description: 'Name of P permission'
|
||||||
|
},
|
||||||
|
'approver': {
|
||||||
|
id: 'permission_admin',
|
||||||
|
defaultMessage: "Approve ({val})",
|
||||||
|
description: 'Name of A permission'
|
||||||
|
},
|
||||||
|
'sharer': {
|
||||||
|
id: 'permission_share',
|
||||||
|
defaultMessage: "Share ({val})",
|
||||||
|
description: 'Name of S permission'
|
||||||
|
},
|
||||||
|
'deleter': {
|
||||||
|
id: 'permission_delete',
|
||||||
|
defaultMessage: "Delete ({val})",
|
||||||
|
description: 'Name of D permission'
|
||||||
|
},
|
||||||
|
'owner': {
|
||||||
|
id: 'permission_owner',
|
||||||
|
defaultMessage: "Owner ({val})",
|
||||||
|
description: 'Name of O permission'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
class PermissionsEditor extends React.Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
@@ -45,16 +92,17 @@ export default class PermissionsEditor extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
const {formatMessage} = this.props.intl;
|
||||||
const all = 'JRWPASDO';
|
const all = 'JRWPASDO';
|
||||||
const names = {
|
const names = {
|
||||||
'J': 'Join (J)',
|
'J': formatMessage(messages.joiner, {val: 'J'}),
|
||||||
'R': 'Read (R)',
|
'R': formatMessage(messages.reader, {val: 'R'}),
|
||||||
'W': 'Write (W)',
|
'W': formatMessage(messages.writer, {val: 'W'}),
|
||||||
'P': 'Get notified (P)',
|
'P': formatMessage(messages.preser, {val: 'P'}),
|
||||||
'A': 'Approve (A)',
|
'A': formatMessage(messages.approver, {val: 'A'}),
|
||||||
'S': 'Share (S)',
|
'S': formatMessage(messages.sharer, {val: 'S'}),
|
||||||
'D': 'Delete (D)',
|
'D': formatMessage(messages.deleter, {val: 'D'}),
|
||||||
'O': 'Owner (O)'
|
'O': formatMessage(messages.owner, {val: 'O'})
|
||||||
};
|
};
|
||||||
|
|
||||||
let skip = this.props.skip || '';
|
let skip = this.props.skip || '';
|
||||||
@@ -88,7 +136,8 @@ export default class PermissionsEditor extends React.Component {
|
|||||||
item={this.props.item}
|
item={this.props.item}
|
||||||
title={this.props.userTitle}
|
title={this.props.userTitle}
|
||||||
avatar={makeImageUrl(this.props.userAvatar ? this.props.userAvatar : null)} /></ul> : null}
|
avatar={makeImageUrl(this.props.userAvatar ? this.props.userAvatar : null)} /></ul> : null}
|
||||||
<label className="small">Permissions</label>
|
<label className="small"><FormattedMessage id="title_permissions"
|
||||||
|
defaultMessage="Permissions" description="Section title"/></label>
|
||||||
<table className="permission-editor">
|
<table className="permission-editor">
|
||||||
{this.props.compare ?
|
{this.props.compare ?
|
||||||
<thead><tr>
|
<thead><tr>
|
||||||
@@ -101,10 +150,16 @@ export default class PermissionsEditor extends React.Component {
|
|||||||
</tbody></table>
|
</tbody></table>
|
||||||
<br />
|
<br />
|
||||||
<div className="dialog-buttons">
|
<div className="dialog-buttons">
|
||||||
<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" />
|
||||||
|
</button>
|
||||||
|
<button className="white" onClick={this.handleCancel}>
|
||||||
|
<FormattedMessage id="button_cancel" />
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export default injectIntl(PermissionsEditor);
|
||||||
|
|||||||
+9
-9
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