all components are translated now

This commit is contained in:
or-else
2018-10-30 15:56:36 +03:00
parent 954b0079a4
commit 8c4e0e9068
20 changed files with 263 additions and 95 deletions
+12
View File
@@ -1448,12 +1448,24 @@ ul.tabbar li.active > a:after {
color: #660;
}
.badge.blue {
border-color: #339;
background-color: #eef;
color: #339;
}
.badge.red {
border-color: #933;
background-color: #fee;
color: #933;
}
.badge.magenta {
border-color: #939;
background-color: #fef;
color: #939;
}
/* Letter tile base style, responsive square box */
.lettertile {
width: 100%;
+1 -1
View File
File diff suppressed because one or more lines are too long
+3 -3
View File
@@ -36,7 +36,7 @@ let messages = globSync(MESSAGES_PATTERN)
let filename = path.join(LANG_DIR, DEFAULT_TRANSLATION);
mkdirpSync(path.dirname(filename));
fs.writeFileSync(filename, JSON.stringify(messages, null, 2));
fs.writeFileSync(filename, JSON.stringify(messages, null, 2)+"\n");
// Find messages present in en.json but missing in translations and vice versa.
// Mark missing or obsolete messages as such in translation files.
@@ -64,7 +64,7 @@ globSync(LANG_PATTERN)
translated[ent[0]] = val;
}
});
fs.writeFileSync(filename, JSON.stringify(translated, null, 2));
fs.writeFileSync(filename, JSON.stringify(translated, null, 2)+"\n");
}
});
@@ -94,4 +94,4 @@ messages = globSync(LANG_PATTERN)
// Write file with all translations combined.
mkdirpSync(path.dirname(FINAL_FILE));
fs.writeFileSync(FINAL_FILE, JSON.stringify(messages, null, 2));
fs.writeFileSync(FINAL_FILE, JSON.stringify(messages, null, 2)+"\n");
+18 -1
View File
@@ -48,6 +48,11 @@
"tabtitle_new_group": "new group",
"tabtitle_group_by_id": "by id",
"search_for_contacts": "Use search to find contacts",
"new_password_placeholder": "Enter new password",
"label_reset_password": "Send a password reset email:",
"credential_email_prompt": "Your registration email",
"button_reset": "Reset",
"button_send_request": "Send request",
"label_server_to_use": "Server to use:",
"label_wire_transport": "Wire transport:",
"button_update": "Update",
@@ -64,6 +69,8 @@
"numeric_confirmation_code_prompt": "Numbers only",
"button_confirm": "Confirm",
"invalid_content": "invalid content",
"badge_you": "you",
"badge_owner": "owner",
"menu_item_info": "Info",
"menu_item_clear_messages": "Clear messages",
"menu_item_clear_messages_for_all": "Clear for All",
@@ -75,13 +82,23 @@
"menu_item_unblock": "Unblock",
"menu_item_block": "Block",
"menu_item_member_delete": "Remove",
"title_group_members": "Group members",
"action_cancel": "cancel",
"upload_finishing": "finishing...",
"no_contacts": "You have no contacts :-(",
"contacts_not_found_short": "No contacts match '{query}'",
"title_group_members": "Group Members",
"title_all_contacts": "All Contacts",
"button_ok": "OK",
"button_cancel": "Cancel",
"download_action": "download",
"label_file_name": "File name:",
"label_content_type": "Content type:",
"label_size": "Size:",
"error_invalid_id": "Invalid ID",
"group_user_id_prompt": "Group or User ID",
"button_subscribe": "Subscribe",
"topic_name_editing_placeholder": "Freeform name of the group",
"button_create": "Create",
"permission_join": "Join ({val})",
"permission_read": "Read ({val})",
"permission_write": "Write ({val})",
+18 -1
View File
@@ -100,5 +100,22 @@
"menu_item_unblock": "Разблокировать",
"menu_item_block": "Заблокировать",
"menu_item_member_delete": "Отписать",
"title_info": "Подробности"
"title_info": "Подробности",
"new_password_placeholder": "Введите новый пароль",
"label_reset_password": "Отправить емейл для смены пароля:",
"credential_email_prompt": "Регистрационный емейл",
"button_reset": "Изменить",
"button_send_request": "Отправить",
"action_cancel": "отменить",
"upload_finishing": "завершение...",
"no_contacts": "Ничего нет :-(",
"contacts_not_found_short": "Нет контактов для запроса '{query}'",
"title_all_contacts": "Все контакты",
"error_invalid_id": "Неверный ID",
"group_user_id_prompt": "ID чата или пользователя",
"button_subscribe": "Подписаться",
"topic_name_editing_placeholder": "Название чата",
"button_create": "Создать",
"badge_you": "вы",
"badge_owner": "влад."
}
+1 -1
View File
@@ -27,7 +27,7 @@ const messages =
allMessages.en;
ReactDOM.render(
<IntlProvider locale={language} messages={messages} >
<IntlProvider locale={language} messages={messages} textComponent={React.Fragment}>
<TinodeWeb />
</IntlProvider>,
document.getElementById('mountPoint')
+37 -3
View File
@@ -49,6 +49,11 @@
"tabtitle_new_group": "new group",
"tabtitle_group_by_id": "by id",
"search_for_contacts": "Use search to find contacts",
"new_password_placeholder": "Enter new password",
"label_reset_password": "Send a password reset email:",
"credential_email_prompt": "Your registration email",
"button_reset": "Reset",
"button_send_request": "Send request",
"label_server_to_use": "Server to use:",
"label_wire_transport": "Wire transport:",
"button_update": "Update",
@@ -65,6 +70,8 @@
"numeric_confirmation_code_prompt": "Numbers only",
"button_confirm": "Confirm",
"invalid_content": "invalid content",
"badge_you": "you",
"badge_owner": "owner",
"menu_item_info": "Info",
"menu_item_clear_messages": "Clear messages",
"menu_item_clear_messages_for_all": "Clear for All",
@@ -76,13 +83,23 @@
"menu_item_unblock": "Unblock",
"menu_item_block": "Block",
"menu_item_member_delete": "Remove",
"title_group_members": "Group members",
"action_cancel": "cancel",
"upload_finishing": "finishing...",
"no_contacts": "You have no contacts :-(",
"contacts_not_found_short": "No contacts match '{query}'",
"title_group_members": "Group Members",
"title_all_contacts": "All Contacts",
"button_ok": "OK",
"button_cancel": "Cancel",
"download_action": "download",
"label_file_name": "File name:",
"label_content_type": "Content type:",
"label_size": "Size:",
"error_invalid_id": "Invalid ID",
"group_user_id_prompt": "Group or User ID",
"button_subscribe": "Subscribe",
"topic_name_editing_placeholder": "Freeform name of the group",
"button_create": "Create",
"permission_join": "Join ({val})",
"permission_read": "Read ({val})",
"permission_write": "Write ({val})",
@@ -167,7 +184,7 @@
"email_dative": "емейлу",
"title_group_members": "Участники",
"download_action": "скачать",
"permission_join": "Присоединяться ({val})",
"permission_join": "Подписываться ({val})",
"permission_read": "Читать ({val})",
"permission_write": "Писать ({val})",
"permission_pres": "Уведомлять ({val})",
@@ -205,6 +222,23 @@
"menu_item_unblock": "Разблокировать",
"menu_item_block": "Заблокировать",
"menu_item_member_delete": "Отписать",
"title_info": "Подробности"
"title_info": "Подробности",
"new_password_placeholder": "Введите новый пароль",
"label_reset_password": "Отправить емейл для смены пароля:",
"credential_email_prompt": "Регистрационный емейл",
"button_reset": "Изменить",
"button_send_request": "Отправить",
"action_cancel": "отменить",
"upload_finishing": "завершение...",
"no_contacts": "Ничего нет :-(",
"contacts_not_found_short": "Нет контактов для запроса '{query}'",
"title_all_contacts": "Все контакты",
"error_invalid_id": "Неверный ID",
"group_user_id_prompt": "ID чата или пользователя",
"button_subscribe": "Подписаться",
"topic_name_editing_placeholder": "Название чата",
"button_create": "Создать",
"badge_you": "вы",
"badge_owner": "влад."
}
}
+3 -1
View File
@@ -2,6 +2,8 @@
import React from 'react';
import { FormattedMessage, defineMessages, injectIntl } from 'react-intl';
import Tinode from 'tinode-sdk';
import AvatarUpload from '../widgets/avatar-upload.jsx';
import CheckBox from '../widgets/checkbox.jsx';
import ContactList from '../widgets/contact-list.jsx';
@@ -225,7 +227,7 @@ class InfoView extends React.Component {
this.setState({private: priv});
this.props.onTopicDescUpdate(this.props.topic,
null,
priv);
priv || Tinode.DEL_CHAR);
}
}
+1 -2
View File
@@ -1,5 +1,4 @@
/* Create new topic and invite users or send an invite */
// Create new topic and invite users or send an invite.
import React from 'react';
import { FormattedMessage } from 'react-intl';
+30 -15
View File
@@ -1,5 +1,6 @@
// A password reset form.
import React from 'react';
import { FormattedMessage } from 'react-intl';
import VisiblePassword from '../widgets/visible-password.jsx';
@@ -29,7 +30,7 @@ export default class PasswordResetView extends React.PureComponent {
if (this.state.token) {
this.props.onReset(this.state.scheme, this.state.password.trim(), this.state.token);
} else {
this.props.onRequest("email", this.state.email.trim());
this.props.onRequest('email', this.state.email.trim());
}
}
@@ -46,25 +47,39 @@ export default class PasswordResetView extends React.PureComponent {
return (
<form id="password-reset-form" onSubmit={this.handleSubmit}>
{reset ?
<VisiblePassword
placeholder="Enter new password"
autoComplete="new-password"
value={this.state.password}
required={true} autoFocus={true}
onChange={this.handlePasswordChange} />
<FormattedMessage id="new_password_placeholder" defaultMessage="Enter new password"
description="Placeholder for entering new password">{
(placeholder) => <VisiblePassword
placeholder={placeholder}
autoComplete="new-password"
value={this.state.password}
required={true} autoFocus={true}
onChange={this.handlePasswordChange} />
}</FormattedMessage>
:
<React.Fragment>
<label htmlFor="inputEmail">Send a password reset email:</label>
<input type="email" id="inputEmail"
placeholder="Your registration email"
autoComplete="email"
value={this.state.email}
onChange={this.handleEmailChange}
required autoFocus />
<label htmlFor="inputEmail">
<FormattedMessage id="label_reset_password"
defaultMessage="Send a password reset email:"
description="Label for password reset field" />
</label>
<FormattedMessage id="credential_email_prompt" defaultMessage="Your registration email"
description="Placeholder for entering email">{
(placeholder) => <input type="email" id="inputEmail"
placeholder={placeholder}
autoComplete="email"
value={this.state.email}
onChange={this.handleEmailChange}
required autoFocus />
}</FormattedMessage>
</React.Fragment>
}
<div className="dialog-buttons">
<button className="blue" type="submit">{reset ? "Reset" : "Send request"}</button>
<button className="blue" type="submit">{reset ?
<FormattedMessage id="button_reset" defaultMessage="Reset" description="Button [Reset]" /> :
<FormattedMessage id="button_send_request" defaultMessage="Send request"
description="Button [Send request]" />
}</button>
</div>
</form>
);
+4 -5
View File
@@ -2,17 +2,16 @@ import React from 'react';
/* Contact's labels: [you], [muted], [blocked], etc */
// FIXME: this class is unused.
export default class ContactBadges_UNUSED_REMOVE extends React.PureComponent {
export default class ContactBadges extends React.PureComponent {
render() {
let badges = null;
if (this.props.badges && this.props.badges.length > 0) {
badges = [];
this.props.badges.map(function(b) {
var style = 'badge' + (b.color ? ' ' + b.color : '');
// Badge names are expected to be unique, so using the name as the key.
badges.push(<span className={style} key={b.name}>{b.name}</span>);
let style = 'badge' + (b.color ? ' ' + b.color : '');
badges.push(<span className={style} key={b.key || b.name}>{b.name}</span>);
});
}
return badges;
return <span>{badges}</span>;
}
};
+22 -7
View File
@@ -1,12 +1,26 @@
// ContactList: component for showing a list of contacts,
// such as a list of group members in a group chat.
import React from 'react';
import { defineMessages, injectIntl } from 'react-intl';
import Contact from './contact.jsx';
import { makeImageUrl } from '../lib/blob-helpers.js';
/* ContactList: component for showing a list of contacts,
* such as a list of group members in a group chat */
export default class ContactList extends React.Component {
const messages = defineMessages({
badge_you: {
id: 'badge_you',
defaultMessage: 'you',
description: 'Badge for indicating the current user'
},
badge_owner: {
id: 'badge_owner',
defaultMessage: 'owner',
description: 'Badge for indicating the owner'
}
});
class ContactList extends React.Component {
render() {
var contactNodes = [];
var showCheckmark = Array.isArray(this.props.topicSelected);
@@ -34,14 +48,14 @@ export default class ContactList extends React.Component {
var badges = [];
if (this.props.showMode) {
if (key === this.props.myUserId) {
badges.push({name: "you", color: 'green'});
badges.push({name: formatMessage(messages.badge_you), color: 'green'});
}
if (c.acs && c.acs.isOwner()) {
badges.push({name: "owner"});
badges.push({name: formatMessage(messages.badge_owner), color: 'blue'});
}
}
var comment = Array.isArray(c.private) ?
c.private.join(', ') : (c.private ? c.private.comment : null);
c.private.join(',') : (c.private ? c.private.comment : null);
contactNodes.push(
<Contact
@@ -79,4 +93,5 @@ export default class ContactList extends React.Component {
);
}
};
/* END ContactList */
export default injectIntl(ContactList);
+4 -11
View File
@@ -3,6 +3,7 @@ import React from 'react';
import { FormattedMessage } from 'react-intl';
import LetterTile from './letter-tile.jsx';
import ContactBadges from './contact-badges.jsx';
import UnreadBadge from './unread-badge.jsx';
export default class Contact extends React.Component {
@@ -37,17 +38,9 @@ export default class Contact extends React.Component {
}
let online = this.props.now ? 'online' : 'offline';
let avatar = this.props.avatar ? this.props.avatar : true;
let badges = [];
if (this.props.badges && this.props.badges.length > 0) {
let count = 0;
this.props.badges.map(function(b) {
let style = 'badge' + (b.color ? ' ' + b.color : '');
badges.push(<span className={style} key={count}>{b.name}</span>);
count ++;
});
}
let badges = this.props.badges.slice();
if (this.props.showMode && this.props.acs) {
badges.push(<span className="badge" key="mode">{this.props.acs.getMode()}</span>);
badges.push({name: this.props.acs.getMode(), key: 'mode'});
}
return (
@@ -68,7 +61,7 @@ export default class Contact extends React.Component {
{this.props.unread > 0 ? <UnreadBadge count={this.props.unread} /> : null}
</div>
{this.props.comment ? <div className="contact-comment">{this.props.comment}</div> : null}
<span>{badges}</span>
<ContactBadges badges={badges} />
</div>
{this.props.showContextMenu ?
<span className="menuTrigger">
+8 -4
View File
@@ -1,6 +1,6 @@
// File uload/download progress indicator with a cancel inside.
import React from 'react';
/* File uload/download progress indicator with a cancel inside */
import { FormattedMessage } from 'react-intl';
export default class FileProgress extends React.PureComponent {
render() {
@@ -8,9 +8,13 @@ export default class FileProgress extends React.PureComponent {
<div className="uploader">
<div><span style={{width: (this.props.progress * 100) + "%"}}></span></div>
{this.props.progress < 0.999 ?
<a href="javascript:;" onClick={this.props.onCancel}><i className="material-icons">close</i> cancel</a>
<a href="javascript:;" onClick={this.props.onCancel}>
<i className="material-icons">close</i> <FormattedMessage id="action_cancel"
defaultMessage="cancel" description="Call to action [cancel]" />
</a>
:
<span>finishing...</span>
<FormattedMessage id="upload_finishing" defaultMessage="finishing..."
description="Notification that upload is finishing" />
}
</div>
);
+21 -5
View File
@@ -5,6 +5,19 @@ import { FormattedMessage, defineMessages, injectIntl } from 'react-intl';
import ChipInput from './chip-input.jsx';
import ContactList from './contact-list.jsx';
const messages = defineMessages({
no_contacts: {
id: 'no_contacts',
defaultMessage: 'You have no contacts :-(',
description: 'Shown in ContactsView when the user has no contacts'
},
contacts_not_found: {
id: 'contacts_not_found_short',
defaultMessage: "No contacts match '{query}'",
description: 'Shown in ContactsView when search returned no results'
}
});
class GroupManager extends React.Component {
constructor(props) {
super(props);
@@ -13,7 +26,7 @@ class GroupManager extends React.Component {
members: props.members,
index: GroupManager.indexMembers(props.members),
contactFilter: '',
noContactsMessage: "You have no contacts :-(",
noContactsMessage: props.intl.formatMessage(messages.no_contacts),
selectedContacts: GroupManager.selectedContacts(props.members)
};
@@ -86,8 +99,8 @@ class GroupManager extends React.Component {
handleContactFilter(val) {
const {formatMessage} = this.props.intl;
const msg = !val ?
"You have no contacts :-(" :
"No contacts match '" + val + "'";
formatMessage(messages.no_contacts) :
formatMessage(messages.contacts_not_found, {query: val});
this.setState({contactFilter: val, noContactsMessage: msg});
}
@@ -135,7 +148,7 @@ class GroupManager extends React.Component {
<div id="group-manager">
<div className="panel-form-row">
<label className="small">
<FormattedMessage id="title_group_members" defaultMessage="Group members" description="Section title" />
<FormattedMessage id="title_group_members" defaultMessage="Group Members" description="Section title" />
</label>
</div>
<div className="panel-form-row">
@@ -147,7 +160,10 @@ class GroupManager extends React.Component {
onChipRemoved={this.handleMemberRemoved} />
</div>
<div className="panel-form-row">
<label className="small">All contacts</label>
<label className="small">
<FormattedMessage id="title_all_contacts" defaultMessage="All Contacts"
description="Section title [All Contacts]" />
</label>
</div>
<ContactList
contacts={this.props.contacts}
+23 -6
View File
@@ -1,6 +1,15 @@
import React from 'react';
import { FormattedMessage, defineMessages, injectIntl } from 'react-intl';
export default class NewTopicById extends React.PureComponent {
const messages = defineMessages({
invalid_id: {
id: 'error_invalid_id',
defaultMessage: 'Invalid ID',
description: 'Error message'
}
});
class NewTopicById extends React.PureComponent {
constructor(props) {
super(props);
@@ -30,7 +39,7 @@ export default class NewTopicById extends React.PureComponent {
if (name.length > 3 && (name.substr(0, 3) == 'usr' || name.substr(0, 3) == 'grp')) {
this.props.onSubmit(name);
} else {
this.props.onError("Invalid ID", 'err');
this.props.onError(this.props.intl.formatMessage(messages.invalid_id), 'err');
}
}
}
@@ -39,14 +48,22 @@ export default class NewTopicById extends React.PureComponent {
return (
<div className="panel-form">
<div className="panel-form-row">
<input type="text" placeholder="Group or User ID"
value={this.state.groupId} onChange={this.handleChange}
onKeyPress={this.handleKeyPress} required />
<FormattedMessage id="group_user_id_prompt" defaultMessage="Group or User ID"
description="Prompt for entering user or group ID">{
(prompt) => <input type="text" placeholder={prompt}
value={this.state.groupId} onChange={this.handleChange}
onKeyPress={this.handleKeyPress} required />
}</FormattedMessage>
</div>
<div className="dialog-buttons">
<button className="blue" onClick={this.handleSubmit}>Subscribe</button>
<button className="blue" onClick={this.handleSubmit}>
<FormattedMessage id="button_subscribe" defaultMessage="Subscribe"
description="Button [Subscribe]" />
</button>
</div>
</div>
);
}
};
export default injectIntl(NewTopicById);
+27 -12
View File
@@ -1,4 +1,5 @@
import React from 'react';
import { FormattedMessage } from 'react-intl';
import AvatarUpload from './avatar-upload.jsx';
import TagManager from './tag-manager.jsx';
@@ -56,25 +57,39 @@ export default class NewTopicGroup extends React.PureComponent {
<form className="panel-form" onSubmit={this.handleSubmit}>
<div className="panel-form-row">
<div className="panel-form-column">
<label className="small" htmlFor="new-topic-fn">Group name</label>
<input type="text" id="new-topic-fn" placeholder="Freeform name of the group"
value={this.state.fn} onChange={this.handleFnChange} autoFocus required />
<label className="small" htmlFor="new-topic-fn">
<FormattedMessage id="label_topic_name" />
</label>
<FormattedMessage id="topic_name_editing_placeholder" defaultMessage="Freeform name of the group"
description="Prompt for entering topic name">{
(placeholder) => <input type="text" id="new-topic-fn" placeholder={placeholder}
value={this.state.fn} onChange={this.handleFnChange} autoFocus required />
}</FormattedMessage>
<br />
<label className="small" htmlFor="new-topic-priv">Private comment</label>
<input type="text" id="new-topic-priv" placeholder="Visible to you only"
value={this.state.private} onChange={this.handlePrivateChange} />
<label className="small" htmlFor="new-topic-priv">
<FormattedMessage id="label_private" />
</label>
<FormattedMessage id="private_editing_placeholder">{
(placeholder) => <input type="text" id="new-topic-priv" placeholder={placeholder}
value={this.state.private} onChange={this.handlePrivateChange} />
}</FormattedMessage>
</div>
<AvatarUpload
onError={this.props.onError}
onImageChanged={this.handleImageChanged} />
</div>
<TagManager
tags={this.state.tags}
activated={true}
onTagsChanged={this.handleTagsChanged}
title="Optional tags (search and discovery)" />
<FormattedMessage id="title_tag_manager">{
(title) => <TagManager
tags={this.state.tags}
activated={true}
onTagsChanged={this.handleTagsChanged}
title={title} />
}</FormattedMessage>
<div className="dialog-buttons">
<button className={submitClasses}>Create</button>
<button className={submitClasses}>
<FormattedMessage id="button_create" defaultMessage="Create"
description="Button [Create]" />
</button>
</div>
</form>
);
+1
View File
@@ -4,6 +4,7 @@ import { FormattedMessage } from 'react-intl';
import ChipInput from './chip-input.jsx';
import { MIN_TAG_LENGTH } from '../config.js';
import { arrayEqual } from '../lib/utils.js';
export default class TagManager extends React.Component {
+23 -11
View File
File diff suppressed because one or more lines are too long
+4 -4
View File
File diff suppressed because one or more lines are too long