Commit 9af137be authored by valzav's avatar valzav
Browse files

first merge: very preleminary and needs testing

parent 36e94887
......@@ -2,16 +2,19 @@ import React from 'react';
import isString from 'lodash/isString';
import isObject from 'lodash/isObject';
import isUndefined from 'lodash/isUndefined';
import { connect } from 'react-redux'
import { IntlProvider, addLocaleData, injectIntl } from 'react-intl';
import store from 'store';
import { DEFAULT_LANGUAGE } from 'config/client_config';
// most of this code creates a wrapper for i18n API.
// this is needed to make i18n future proof
/*
module exports two functions: translate and translateHtml
usage example:
translate('reply_to_user', {username: 'undeadlol1') == 'Reply to undeadlol1'
translateHtml works the same, expcept it renders string with html tags in it
module exports two functions: translate and translateHtml
usage example:
translate('reply_to_user', {username: 'undeadlol1') == 'Reply to undeadlol1'
translateHtml works the same, expcept it renders string with html tags in it
*/
// locale data is needed for various messages, ie 'N minutes ago'
......@@ -28,7 +31,7 @@ import { ru } from './locales/ru';
import { fr } from './locales/fr';
import { es } from './locales/es';
import { it } from './locales/it';
const translations = {
const messages = {
en: en,
ru: ru,
fr: fr,
......@@ -38,9 +41,18 @@ const translations = {
// exported function placeholders
// this is needed for proper export before react-intl functions with locale data,
// will be properly created (they depend on react props and context,
// which is not available until component is being created)
let translate = () => {};
// will be properly created (they depend on react props and context),
// which is not available until component is being created
//
/*
this placeholder is needed for usage outside of react. In server side code and in static html files.
This function is very simple, it does NOT support dynamic values (for example: translate('your_email_is', {email: 'x@y.com'})). Use it carefully
*/
let translate = string => {
let language = DEFAULT_LANGUAGE
if (process.env.BROWSER) language = store.get('language') || DEFAULT_LANGUAGE
return messages[language][string]
};
let translateHtml = () => {};
let translatePlural = () => {};
......@@ -105,24 +117,30 @@ class Translator extends React.Component {
// Define user's language. Different browsers have the user locale defined
// on different fields on the `navigator` object, so we make sure to account
// for these different by checking all of them
let language = 'en';
// while Server Side Rendering is in process, 'navigator' is undefined
let language = this.props.locale; // usually 'en'
if (process.env.BROWSER) {
language = navigator ? (navigator.languages && navigator.languages[0])
|| navigator.language
|| navigator.userLanguage
: 'en';
const storredLanguage = store.get('language')
if (storredLanguage) language = storredLanguage
}
//Split locales with a region code (ie. 'en-EN' to 'en')
const languageWithoutRegionCode = language.toLowerCase().split(/[_-]+/)[0];
// TODO: don't forget to add Safari polyfill
// to ensure dynamic language change, "key" property with same "locale" info must be added
// see: https://github.com/yahoo/react-intl/wiki/Components#multiple-intl-contexts
let messages = translations[languageWithoutRegionCode]
return <IntlProvider locale={languageWithoutRegionCode} key={languageWithoutRegionCode} messages={messages}>
// let language = DEFAULT_LANGUAGE; // usually 'en'
// while Server Side Rendering is in process, 'navigator' is undefined
// currently commented out, because in golos we need only russian
// if (process.env.BROWSER) language = navigator
// ? (navigator.languages && navigator.languages[0])
// || navigator.language
// || navigator.userLanguage
// : DEFAULT_LANGUAGE;
//Split locales with a region code (ie. 'en-EN' to 'en')
const languageWithoutRegionCode = language.toLowerCase().split(/[_-]+/)[0];
return <IntlProvider
// to ensure dynamic language change, "key" property with same "locale" info must be added
// see: https://github.com/yahoo/react-intl/wiki/Components#multiple-intl-contexts
key={languageWithoutRegionCode}
defaultLocale={DEFAULT_LANGUAGE}
locale={languageWithoutRegionCode}
messages={messages[languageWithoutRegionCode]}
>
<div>
<DummyComponentToExportProps />
{this.props.children}
......@@ -133,4 +151,13 @@ class Translator extends React.Component {
export { translate, translateHtml, translatePlural }
export default Translator
export default connect(
// mapStateToProps
(state, ownProps) => {
const locale = state.user.get('locale')
return {
...ownProps,
locale
}
}
)(Translator)
<?xml version="1.0" encoding="utf-8"?>
<browserconfig>
<msapplication>
<tile>
<square150x150logo src="/images/favicons/mstile-150x150.png"/>
<TileColor>#ffffff</TileColor>
</tile>
</msapplication>
</browserconfig>
{
"name": "Golos",
"icons": [
{
"src": "\/images\/favicons\/android-chrome-192x192.png",
"sizes": "192x192",
"type": "image\/png"
},
{
"src": "\/images\/favicons\/android-chrome-512x512.png",
"sizes": "512x512",
"type": "image\/png"
}
],
"theme_color": "#ffffff",
"display": "standalone"
}
......@@ -3,6 +3,7 @@ import {connect} from 'react-redux'
import Link from 'app/components/elements/Link'
import g from 'app/redux/GlobalReducer'
import links from 'app/utils/Links'
import { translate } from 'app/Translator';
/** @deprecated */
class CardView extends React.Component {
......@@ -43,13 +44,13 @@ class CardView extends React.Component {
const youTubeImage = links.youTube.test(link)
return <span className="Card">
{image && !youTubeImage && <div>
{canEdit && <div>(<a onClick={this.onCloseImage}>remove</a>)<br /></div>}
{canEdit && <div>(<a onClick={this.onCloseImage}>{translate('remove')}</a>)<br /></div>}
<Link href={link}>
<img src={image} alt={alt} />
</Link>
</div>}
{description && <div>
{canEdit && <span>(<a onClick={this.onCloseDescription}>remove</a>)</span>}
{canEdit && <span>(<a onClick={this.onCloseDescription}>{translate('remove')}</a>)</span>}
<Link href={link}>
<blockquote>{description}</blockquote>
</Link>
......
......@@ -2,6 +2,7 @@ import React from 'react';
import {connect} from 'react-redux'
import shouldComponentUpdate from 'app/utils/shouldComponentUpdate'
import {cleanReduxInput} from 'app/utils/ReduxForms'
import { translate } from '../../Translator.js';
class CategorySelector extends React.Component {
static propTypes = {
......@@ -21,7 +22,6 @@ class CategorySelector extends React.Component {
}
static defaultProps = {
autoComplete: 'on',
placeholder: 'Tag (up to 5 tags), the first tag is your main category.',
id: 'CategorySelectorId',
isEdit: false,
}
......@@ -60,7 +60,7 @@ class CategorySelector extends React.Component {
const categorySelect = (
<select {...cleanReduxInput(this.props)} onChange={this.categorySelectOnChange} ref="categoryRef" tabIndex={tabIndex} disabled={disabled}>
<option value="">Select a tag...</option>
<option value="">{translate('select_a_tag')}...</option>
{categoryOptions}
<option value="new">{this.props.placeholder}</option>
</select>
......@@ -73,25 +73,25 @@ class CategorySelector extends React.Component {
}
}
export function validateCategory(category, required = true) {
if(!category || category.trim() === '') return required ? 'Required' : null
if(!category || category.trim() === '') return required ? translate( 'required' ) : null
const cats = category.trim().split(' ')
return (
// !category || category.trim() === '' ? 'Required' :
cats.length > 5 ? 'Please use only five categories' :
cats.find(c => c.length > 24) ? 'Maximum tag length is 24 characters' :
cats.find(c => c.split('-').length > 2) ? 'Use only one dash' :
cats.find(c => c.indexOf(',') >= 0) ? 'Use spaces to separate tags' :
cats.find(c => /[A-Z]/.test(c)) ? 'Use only lowercase letters' :
cats.find(c => !/^[a-z0-9-#]+$/.test(c)) ? 'Use only lowercase letters, digits and one dash' :
cats.find(c => !/^[a-z-#]/.test(c)) ? 'Must start with a letter' :
cats.find(c => !/[a-z0-9]$/.test(c)) ? 'Must end with a letter or number' :
cats.length > 5 ? translate('use_limitied_amount_of_categories', {amount: 5}) :
cats.find(c => c.length > 24) ? translate('maximum_tag_length_is_24_characters') :
cats.find(c => c.split('-').length > 2) ? translate('use_one_dash') :
cats.find(c => c.indexOf(',') >= 0) ? translate('use_spaces_to_separate_tags') :
cats.find(c => /[A-Z]/.test(c)) ? translate('use_only_lowercase_letters') :
cats.find(c => !/^[a-z0-9-#]+$/.test(c)) ? translate('use_only_allowed_characters') :
cats.find(c => !/^[a-z-#]/.test(c)) ? translate('must_start_with_a_letter') :
cats.find(c => !/[a-z0-9]$/.test(c)) ? translate('must_end_with_a_letter_or_number') :
null
)
}
export default connect((state, ownProps) => {
const trending = state.global.get('category_idx').get('trending')
return {
trending,
...ownProps,
}
// apply translations
// they are used here because default prop can't acces intl property
const placeholder = translate('tag_your_story');
return { trending, placeholder, ...ownProps, }
})(CategorySelector);
......@@ -13,9 +13,9 @@ import Icon from 'app/components/elements/Icon';
import Userpic from 'app/components/elements/Userpic';
import transaction from 'app/redux/Transaction'
import {List} from 'immutable'
import pluralize from 'pluralize';
import { translate } from 'app/Translator';
export function sortComments( g, comments, sort_order ){
export function sortComments( g, comments, sort_order ) {
function netNegative(a) {
return a.get("net_rshares") < 0;
......@@ -85,7 +85,7 @@ class CommentImpl extends React.Component {
// html props
global: React.PropTypes.object.isRequired,
content: React.PropTypes.string.isRequired,
sort_order: React.PropTypes.oneOf(['active', 'update', 'created', 'trending']).isRequired,
sort_order: React.PropTypes.oneOf(['active', 'updated', 'created', 'trending']).isRequired,
root: React.PropTypes.bool,
showNegativeComments: React.PropTypes.bool,
onHide: React.PropTypes.func,
......@@ -166,7 +166,7 @@ class CommentImpl extends React.Component {
/**
* - `hide` is based on author reputation, and will hide the entire post on initial render.
* - `hide_body` is true when comment rshares OR author rep is negative.
* - `hide_body` is true when comment rshares OR author rep is negative.
* it hides the comment body (but not the header) until the "reveal comment" link is clicked.
*/
_checkHide(props) {
......@@ -216,7 +216,7 @@ class CommentImpl extends React.Component {
let g = this.props.global;
const dis = g.get('content').get(this.props.content);
if (!dis) {
return <div>Loading...</div>
return <div>{translate('loading')}...</div>
}
const comment = dis.toJS();
if(!comment.stats) {
......@@ -259,10 +259,10 @@ class CommentImpl extends React.Component {
controls = <div>
<Voting post={post} />
{!readonly &&
<span className="Comment__footer__controls">
{depth < 6 && <a onClick={onShowReply}>Reply</a>}
{' '}{showEditOption && <a onClick={onShowEdit}>Edit</a>}
{' '}{showDeleteOption && <a onClick={onDeletePost}>Delete</a>}
<span className="Comment__footer__controls">
{depth < 6 && <a onClick={onShowReply}>translate('reply')</a>}
{' '}{showEditOption && <a onClick={onShowEdit}>translate('edit')</a>}
{' '}{showDeleteOption && <a onClick={onDeletePost}>translate('delete')</a>}
</span>}
</div>;
}
......@@ -311,7 +311,7 @@ class CommentImpl extends React.Component {
<div className="Comment__header">
<div className="Comment__header_collapse">
<Voting post={post} flag />
<a title="Collapse/Expand" onClick={this.toggleCollapsed}>{ this.state.collapsed ? '[+]' : '[-]' }</a>
<a title={translate('collapse_or_expand')} onClick={this.toggleCollapsed}>{ this.state.collapsed ? '[+]' : '[-]' }</a>
</div>
<span className="Comment__header-user">
<Icon name="user" className="Comment__Userpic-small" />
......@@ -324,9 +324,9 @@ class CommentImpl extends React.Component {
{ (this.state.collapsed || hide_body) &&
<Voting post={post} showList={false} /> }
{ this.state.collapsed && comment.children > 0 &&
<span className="marginLeft1rem">{pluralize('replies', comment.children, true)}</span>}
<span className="marginLeft1rem">{translate('reply_count', {replyCount: comment.children})}</span>}
{ !this.state.collapsed && hide_body &&
<a className="marginLeft1rem" onClick={this.revealBody}>reveal comment</a>}
<a className="marginLeft1rem" onClick={this.revealBody}>{translate('reveal_comment')}</a>}
</div>
<div className="Comment__body entry-content">
{showEdit ? renderedEditor : body}
......@@ -378,7 +378,7 @@ const Comment = connect(
dispatch(transaction.actions.broadcastOperation({
type: 'delete_comment',
operation: {author, permlink},
confirm: 'Are you sure?'
confirm: translate('are_you_sure'),
}))
},
})
......
......@@ -2,6 +2,7 @@ import React from 'react';
import { Link } from 'react-router';
import TimeAgoWrapper from 'app/components/elements/TimeAgoWrapper';
import Icon from 'app/components/elements/Icon';
import { translate } from 'app/Translator';
export default class PostHistoryRow extends React.Component {
......@@ -18,9 +19,9 @@ export default class PostHistoryRow extends React.Component {
let permlink = op[1].op[1].permlink;
let in_reply_to = <span></span>;
if( parent_author && parent_author != context )
in_reply_to = <span>in reply to <Link to={parent_link}>@{parent_author}</Link></span>;
in_reply_to = <span>{translate('in_reply_to') + ' '}<Link to={parent_link}>@{parent_author}</Link></span>;
else if( parent_author == context )
in_reply_to = <span><Link to={author_link}>@{author}</Link> replied to {parent_author}</span>;
in_reply_to = <span><Link to={author_link}>@{author}</Link> {' ' + translate('replied_to') + ' ' + parent_author}</span>;
// const content_markdown = op[1].op[1].body;
// const body = (<MarkdownViewer formId={} text={content_markdown} jsonMetadata={} />)
......
......@@ -2,6 +2,7 @@ import React from 'react';
import { Link } from 'react-router';
import TimeAgoWrapper from 'app/components/elements/TimeAgoWrapper';
import Icon from 'app/components/elements/Icon';
import { translate } from 'app/Translator';
export default class VoteHistoryRow extends React.Component {
......@@ -18,9 +19,9 @@ export default class VoteHistoryRow extends React.Component {
let permlink = op[1].op[1].permlink;
let in_reply_to = <span></span>;
if( parent_author && parent_author != context )
in_reply_to = <span>in reply to <Link to={parent_link}>@{parent_author}</Link></span>;
in_reply_to = <span>{translate('in_reply_to') + ' '} <Link to={parent_link}>@{parent_author}</Link></span>;
else if( parent_author == context )
in_reply_to = <span><Link to={author_link}>@{author}</Link> replied to {parent_author}</span>;
in_reply_to = <span><Link to={author_link}>@{author}</Link>{' ' + translate('replied_to')}{parent_author}</span>;
// const content_markdown = op[1].op[1].body;
// const body = (<MarkdownViewer formId={} text={content_markdown} jsonMetadata={} />)
......
import React, { Component } from 'react';
import { Field, reduxForm } from 'redux-form';
// change it field by field
class ChangeAccountMeta extends Component {
render() {
const { handleSubmit } = this.props;
return (
<form onSubmit={handleSubmit}>
<div>
<label htmlFor="metaKey">meta key</label>
<Field name="metaKey" component="input" type="text"/>
</div>
<div>
<label htmlFor="metaValue">meta value</label>
<Field name="metaValue" component="input" type="email"/>
</div>
<div>
<label htmlFor="passord">First Name</label>
<Field name="password" component="input" type="password"/>
</div>
<button type="submit">Submit</button>
</form>
);
}
}
// Decorate the form component
ChangeAccountMeta = reduxForm({
form: 'changeAccountMeta' // a unique name for this form
})(ChangeAccountMeta);
import React from 'react'
import transaction from 'app/redux/Transaction'
import LoadingIndicator from 'app/components/elements/LoadingIndicator'
import {PrivateKey} from 'shared/ecc'
import {key_utils} from 'shared/ecc'
import Apis from 'shared/api_client/ApiInstances'
import {cleanReduxInput} from 'app/utils/ReduxForms'
import { translate, translateHtml } from 'app/Translator';
import { FormattedHTMLMessage } from 'react-intl';
import
class changeAccountMeta extends React.Component {
static propTypes = {
// HTML properties
username: string,
password: string,
key: string,
value: string,
meta: string,
}
consctructor(){
super(props)
this.state = {accountName: props.username, generated: false}
// this.onNameChange = this.onNameChange.bind(this)
// this.generateWif = this.generateWif.bind(this)
}
dispatchSubmit = () => {
render(){
return
<span className="changeAccountMeta"><form onSubmit={handleSubmit(() => {this.dispatchSubmit()})}>
{/* tests area for current development */}
<div className="column small-12">
<input type="hidden" id="test-form-meta-key" value="upic"/>
{/* external url */}
<input type="hidden" id="test-form-meta-value" value="https://cyber.fund/images/cyberFund.svg"/>
<input type="hidden" id="test-form-usernmae" value="tort"/>
<input type="password" id="test-form-password" disabled={loading}/>
<button onClick={this.testFormSubmit.bind(this)} disabled={loading} className="red">X X X X X</button>
</div>
</form></span>
}
}
export default reduxForm(
{ form: 'changeAccountMeta', fields: ['password', 'key', 'value'] },
// mapStateToProps
(state, ownProps) => {
//const {authType} = ownProps
//const enable2fa = authType == null
return {
...ownProps, enable2fa,
validate: keyValidate,
initialValues: {twofa: false, password: ownProps.defaultPassword},
}
},
// mapDispatchToProps
dispatch => ({
changeAccountMeta: (
meta, accountName, signingKey,success, error
) => {
dispatch(transaction.actions.broadcastOperation(
{type: 'update_account_meta',
operation: {json_meta: meta, account_name: accountName},
}))
}
})
)(changeAccountMeta)
......@@ -7,6 +7,8 @@ import {key_utils} from 'shared/ecc'
import Apis from 'shared/api_client/ApiInstances'
import {validate_account_name} from 'app/utils/ChainValidation'
import {cleanReduxInput} from 'app/utils/ReduxForms'
import { translate, translateHtml } from 'app/Translator';
import { FormattedHTMLMessage } from 'react-intl';
const {string, oneOf} = React.PropTypes
......@@ -40,9 +42,8 @@ class ChangePassword extends React.Component {
if (name.length > 0) {
nameError = validate_account_name(name);
if (!nameError) {
this.setState({nameError: ''});
promise = Apis.db_api('get_accounts', [name]).then(res => {
return !(res && res.length > 0) ? 'Account not found' : '';
return !(res && res.length > 0) ? translate('account_not_found') : '';
});
}
}
......@@ -85,7 +86,7 @@ class ChangePassword extends React.Component {
if (!process.env.BROWSER) { // don't render this page on the server
return <div className="row">
<div className="column">
Loading..
{translate('loading')}..
</div>
</div>;
}
......@@ -98,7 +99,7 @@ class ChangePassword extends React.Component {
console.error('Missing priorAuthKey')
const error2 = /Missing Owner Authority/.test(error) ?
<span>This is the wrong password. Do you need to <a href="/recover_account_step_1">recover your account</a>?</span> :
<span>{translate('this_is_wrong_password')}. {translate('do_you_need_to') + ' '}<a href="/recover_account_step_1">{translate('recover_your_account')}</a>?</span> :
error;
const {accountName, nameError} = this.state;
......@@ -107,34 +108,28 @@ class ChangePassword extends React.Component {
return (
<span className="ChangePassword">
<form onSubmit={handleSubmit(() => {this.dispatchSubmit()})}>
{username && <h4>Reset {username}&apos;s Password</h4>}
{username && <h4>{translate('reset_usernames_password', {username})}</h4>}
{authType ?
<p>This will update {username}&apos; {authType} key.</p> :
<p>{translate('this_will_update_usernames_authtype_key', {
username, authType
})}</p> :
<div className="ChangePassword__rules">
<hr />
<p>
The first rule of Steemit is: Do not lose your password.<br />
The second rule of Steemit is: Do <strong>not</strong> lose your password.<br />
The third rule of Steemit is: We cannot recover your password.<br />
The fourth rule: If you can remember the password, it&apos;s not secure.<br />
The fifth rule: Use only randomly-generated passwords.<br />
The sixth rule: Do not tell anyone your password.<br />
The seventh rule: Always back up your password.
</p>
<hr />
<p> <FormattedHTMLMessage id="the_rules_of_APP_NAME" /> </p>
<hr />
</div>
}
<div className={nameError ? 'error' : ''}>
<label>Account Name
<label>{translate('account_name')}
<input type="text" disabled={readOnlyAccountName} autoComplete="off" value={accountName} onChange={this.onNameChange} />
</label>
<p className="help-text">{nameError}</p>
</div>
<br />
<label>
<div className="float-right"><a href="/recover_account_step_1">Recover Account</a></div>
Current Password
<div className="float-right"><a href="/recover_account_step_1">{translate('recover_password')}</a></div>
{translate('current_password')}
<br />
<input {...cleanReduxInput(password)} type="password" disabled={loading} />
</label>
......@@ -143,7 +138,7 @@ class ChangePassword extends React.Component {
<br></br>
<label>
Generated Password <span className="secondary">(new)</span><br />
{translate('generated_password') + ' ' } <span className="secondary">({translate('new')})</span><br />
</label>
{generated &&
<span>
......@@ -152,17 +147,17 @@ class ChangePassword extends React.Component {
<div className="overflow-ellipsis"><code style={{display: 'block', padding: '0.2rem 0.5rem', background: 'white', color: '#c7254e', wordWrap: 'break-word', fontSize: '100%', textAlign: 'center'}}>{newWif}</code></div>
</div>
<label className="ChangePassword__backup_text">
Back it up by storing in your password manager or a text file.
{translate('backup_password_by_storing_it')}.
</label>
</span>
||
<center><button type="button" className="button hollow" onClick={this.generateWif}>Click to generate password</button></center>
<center><button type="button" className="button hollow" onClick={this.generateWif}>{translate('click_to_generate_password')}</button></center>
}
<br></br>
<label>
Re-enter Generated Password
{translate('re_enter_generate_password')}
<br />
<input {...cleanReduxInput(confirmPassword)} type="password" disabled={loading} />
</label>
......@@ -170,20 +165,20 @@ class ChangePassword extends React.Component {
<br />
<label><input {...cleanReduxInput(confirmCheck)} type="checkbox" /> I understand that Steemit cannot recover lost passwords.</label>