diff options
Diffstat (limited to 'packages/website/ts')
24 files changed, 684 insertions, 288 deletions
diff --git a/packages/website/ts/components/footer.tsx b/packages/website/ts/components/footer.tsx index a0f1a0c96..810460cac 100644 --- a/packages/website/ts/components/footer.tsx +++ b/packages/website/ts/components/footer.tsx @@ -1,9 +1,13 @@ import * as _ from 'lodash'; +import DropDownMenu from 'material-ui/DropDownMenu'; +import MenuItem from 'material-ui/MenuItem'; import * as React from 'react'; import { Link } from 'react-router-dom'; -import { WebsitePaths } from 'ts/types'; +import { Dispatcher } from 'ts/redux/dispatcher'; +import { Deco, Key, Language, WebsitePaths } from 'ts/types'; import { colors } from 'ts/utils/colors'; import { constants } from 'ts/utils/constants'; +import { Translate } from 'ts/utils/translate'; interface MenuItemsBySection { [sectionName: string]: FooterMenuItem[]; @@ -15,105 +19,114 @@ interface FooterMenuItem { isExternal?: boolean; } -enum Sections { - Documentation = 'Documentation', - Community = 'Community', - Organization = 'Organization', -} - const ICON_DIMENSION = 16; -const menuItemsBySection: MenuItemsBySection = { - Documentation: [ - { - title: '0x.js', - path: WebsitePaths.ZeroExJs, - }, - { - title: '0x Smart Contracts', - path: WebsitePaths.SmartContracts, - }, - { - title: '0x Connect', - path: WebsitePaths.Connect, - }, - { - title: 'Whitepaper', - path: WebsitePaths.Whitepaper, - isExternal: true, - }, - { - title: 'Wiki', - path: WebsitePaths.Wiki, - }, - { - title: 'FAQ', - path: WebsitePaths.FAQ, - }, - ], - Community: [ - { - title: 'Rocket.chat', - isExternal: true, - path: constants.URL_ZEROEX_CHAT, - }, - { - title: 'Blog', - isExternal: true, - path: constants.URL_BLOG, - }, - { - title: 'Twitter', - isExternal: true, - path: constants.URL_TWITTER, - }, - { - title: 'Reddit', - isExternal: true, - path: constants.URL_REDDIT, - }, - { - title: 'Forum', - isExternal: true, - path: constants.URL_DISCOURSE_FORUM, - }, - ], - Organization: [ - { - title: 'About', - isExternal: false, - path: WebsitePaths.About, - }, - { - title: 'Careers', - isExternal: true, - path: constants.URL_ANGELLIST, - }, - { - title: 'Contact', - isExternal: true, - path: 'mailto:team@0xproject.com', - }, - ], -}; + const linkStyle = { color: colors.white, cursor: 'pointer', }; -const titleToIcon: { [title: string]: string } = { - 'Rocket.chat': 'rocketchat.png', - Blog: 'medium.png', - Twitter: 'twitter.png', - Reddit: 'reddit.png', - Forum: 'discourse.png', +const languageToMenuTitle = { + [Language.English]: 'English', + [Language.Russian]: 'Русский', + [Language.Spanish]: 'Español', + [Language.Korean]: '한국어', + [Language.Chinese]: '中文', }; -export interface FooterProps {} +export interface FooterProps { + translate: Translate; + dispatcher: Dispatcher; +} -interface FooterState {} +interface FooterState { + selectedLanguage: Language; +} export class Footer extends React.Component<FooterProps, FooterState> { + constructor(props: FooterProps) { + super(); + this.state = { + selectedLanguage: props.translate.getLanguage(), + }; + } public render() { + const menuItemsBySection: MenuItemsBySection = { + [Key.Documentation]: [ + { + title: '0x.js', + path: WebsitePaths.ZeroExJs, + }, + { + title: this.props.translate.get(Key.SmartContracts, Deco.Cap), + path: WebsitePaths.SmartContracts, + }, + { + title: this.props.translate.get(Key.Connect, Deco.Cap), + path: WebsitePaths.Connect, + }, + { + title: this.props.translate.get(Key.Whitepaper, Deco.Cap), + path: WebsitePaths.Whitepaper, + isExternal: true, + }, + { + title: this.props.translate.get(Key.Wiki, Deco.Cap), + path: WebsitePaths.Wiki, + }, + { + title: this.props.translate.get(Key.Faq, Deco.Cap), + path: WebsitePaths.FAQ, + }, + ], + [Key.Community]: [ + { + title: this.props.translate.get(Key.RocketChat, Deco.Cap), + isExternal: true, + path: constants.URL_ZEROEX_CHAT, + }, + { + title: this.props.translate.get(Key.Blog, Deco.Cap), + isExternal: true, + path: constants.URL_BLOG, + }, + { + title: 'Twitter', + isExternal: true, + path: constants.URL_TWITTER, + }, + { + title: 'Reddit', + isExternal: true, + path: constants.URL_REDDIT, + }, + { + title: this.props.translate.get(Key.Forum, Deco.Cap), + isExternal: true, + path: constants.URL_DISCOURSE_FORUM, + }, + ], + [Key.Organization]: [ + { + title: this.props.translate.get(Key.About, Deco.Cap), + isExternal: false, + path: WebsitePaths.About, + }, + { + title: this.props.translate.get(Key.Careers, Deco.Cap), + isExternal: true, + path: constants.URL_ANGELLIST, + }, + { + title: this.props.translate.get(Key.Contact, Deco.Cap), + isExternal: true, + path: 'mailto:team@0xproject.com', + }, + ], + }; + const languageMenuItems = _.map(languageToMenuTitle, (menuTitle: string, language: Language) => { + return <MenuItem key={menuTitle} value={language} primaryText={menuTitle} />; + }); return ( <div className="relative pb4 pt2" style={{ backgroundColor: colors.darkerGrey }}> <div className="mx-auto max-width-4 md-px2 lg-px0 py4 clearfix" style={{ color: colors.white }}> @@ -132,25 +145,34 @@ export class Footer extends React.Component<FooterProps, FooterState> { > © ZeroEx, Intl. </div> + <div className="pt4 center"> + <DropDownMenu + labelStyle={{ color: colors.white }} + value={this.state.selectedLanguage} + onChange={this._updateLanguage.bind(this)} + > + {languageMenuItems} + </DropDownMenu> + </div> </div> </div> <div className="col lg-col-8 md-col-8 col-12 lg-pl4 md-pl4"> <div className="col lg-col-4 md-col-4 col-12"> <div className="lg-right md-right sm-center"> - {this._renderHeader(Sections.Documentation)} - {_.map(menuItemsBySection[Sections.Documentation], this._renderMenuItem.bind(this))} + {this._renderHeader(Key.Documentation)} + {_.map(menuItemsBySection[Key.Documentation], this._renderMenuItem.bind(this))} </div> </div> <div className="col lg-col-4 md-col-4 col-12 lg-pr2 md-pr2"> <div className="lg-right md-right sm-center"> - {this._renderHeader(Sections.Community)} - {_.map(menuItemsBySection[Sections.Community], this._renderMenuItem.bind(this))} + {this._renderHeader(Key.Community)} + {_.map(menuItemsBySection[Key.Community], this._renderMenuItem.bind(this))} </div> </div> <div className="col lg-col-4 md-col-4 col-12"> <div className="lg-right md-right sm-center"> - {this._renderHeader(Sections.Organization)} - {_.map(menuItemsBySection[Sections.Organization], this._renderMenuItem.bind(this))} + {this._renderHeader(Key.Organization)} + {_.map(menuItemsBySection[Key.Organization], this._renderMenuItem.bind(this))} </div> </div> </div> @@ -166,17 +188,22 @@ export class Footer extends React.Component<FooterProps, FooterState> { ); } private _renderMenuItem(item: FooterMenuItem) { + const titleToIcon: { [title: string]: string } = { + [this.props.translate.get(Key.RocketChat, Deco.Cap)]: 'rocketchat.png', + [this.props.translate.get(Key.Blog, Deco.Cap)]: 'medium.png', + Twitter: 'twitter.png', + Reddit: 'reddit.png', + [this.props.translate.get(Key.Forum, Deco.Cap)]: 'discourse.png', + }; const iconIfExists = titleToIcon[item.title]; return ( <div key={item.title} className="sm-center" style={{ fontSize: 13, paddingTop: 25 }}> {item.isExternal ? ( <a className="text-decoration-none" style={linkStyle} target="_blank" href={item.path}> {!_.isUndefined(iconIfExists) ? ( - <div className="sm-mx-auto" style={{ width: 65 }}> - <div className="flex"> - <div className="pr1">{this._renderIcon(iconIfExists)}</div> - <div>{item.title}</div> - </div> + <div className="inline-block"> + <div className="pr1 table-cell">{this._renderIcon(iconIfExists)}</div> + <div className="table-cell">{item.title}</div> </div> ) : ( item.title @@ -195,9 +222,8 @@ export class Footer extends React.Component<FooterProps, FooterState> { </div> ); } - private _renderHeader(title: string) { + private _renderHeader(key: Key) { const headerStyle = { - textTransform: 'uppercase', color: colors.grey400, letterSpacing: 2, fontFamily: 'Roboto Mono', @@ -205,8 +231,14 @@ export class Footer extends React.Component<FooterProps, FooterState> { }; return ( <div className="lg-pb2 md-pb2 sm-pt4" style={headerStyle}> - {title} + {this.props.translate.get(key, Deco.Upper)} </div> ); } + private _updateLanguage(e: any, index: number, value: Language) { + this.setState({ + selectedLanguage: value, + }); + this.props.dispatcher.updateSelectedLanguage(value); + } } diff --git a/packages/website/ts/components/portal.tsx b/packages/website/ts/components/portal.tsx index 0409f28c0..4871997ac 100644 --- a/packages/website/ts/components/portal.tsx +++ b/packages/website/ts/components/portal.tsx @@ -27,6 +27,7 @@ import { BlockchainErrs, HashData, Order, ProviderType, ScreenWidths, TokenByAdd import { colors } from 'ts/utils/colors'; import { configs } from 'ts/utils/configs'; import { constants } from 'ts/utils/constants'; +import { Translate } from 'ts/utils/translate'; import { utils } from 'ts/utils/utils'; const THROTTLE_TIMEOUT = 100; @@ -52,6 +53,7 @@ export interface PortalAllProps { location: Location; flashMessage?: string | React.ReactNode; lastForceTokenStateRefetch: number; + translate: Translate; } interface PortalAllState { @@ -166,6 +168,7 @@ export class Portal extends React.Component<PortalAllProps, PortalAllState> { blockchainIsLoaded={this.props.blockchainIsLoaded} location={this.props.location} blockchain={this._blockchain} + translate={this.props.translate} /> <div id="portal" className="mx-auto max-width-4" style={{ width: '100%' }}> <Paper className="mb3 mt2"> @@ -259,7 +262,7 @@ export class Portal extends React.Component<PortalAllProps, PortalAllState> { /> )} </div> - <Footer />; + <Footer translate={this.props.translate} dispatcher={this.props.dispatcher} /> </div> ); } diff --git a/packages/website/ts/components/top_bar/top_bar.tsx b/packages/website/ts/components/top_bar/top_bar.tsx index a412007f2..2723c2218 100644 --- a/packages/website/ts/components/top_bar/top_bar.tsx +++ b/packages/website/ts/components/top_bar/top_bar.tsx @@ -14,9 +14,10 @@ import { Identicon } from 'ts/components/ui/identicon'; import { DocsInfo } from 'ts/pages/documentation/docs_info'; import { NestedSidebarMenu } from 'ts/pages/shared/nested_sidebar_menu'; import { Dispatcher } from 'ts/redux/dispatcher'; -import { DocsMenu, MenuSubsectionsBySection, ProviderType, Styles, WebsitePaths } from 'ts/types'; +import { Deco, DocsMenu, Key, MenuSubsectionsBySection, ProviderType, Styles, WebsitePaths } from 'ts/types'; import { colors } from 'ts/utils/colors'; import { constants } from 'ts/utils/constants'; +import { Translate } from 'ts/utils/translate'; interface TopBarProps { userAddress?: string; @@ -28,6 +29,7 @@ interface TopBarProps { dispatcher?: Dispatcher; blockchainIsLoaded: boolean; location: Location; + translate: Translate; docsVersion?: string; availableDocVersions?: string[]; menu?: DocsMenu; @@ -95,10 +97,16 @@ export class TopBar extends React.Component<TopBarProps, TopBarState> { <MenuItem style={{ fontSize: styles.menuItem.fontSize }} primaryText="0x.js" /> </Link>, <Link key="subMenuItem-smartContracts" to={WebsitePaths.SmartContracts} className="text-decoration-none"> - <MenuItem style={{ fontSize: styles.menuItem.fontSize }} primaryText="Smart Contracts" /> + <MenuItem + style={{ fontSize: styles.menuItem.fontSize }} + primaryText={this.props.translate.get(Key.SmartContract, Deco.CapWords)} + /> </Link>, <Link key="subMenuItem-0xconnect" to={WebsitePaths.Connect} className="text-decoration-none"> - <MenuItem style={{ fontSize: styles.menuItem.fontSize }} primaryText="0x Connect" /> + <MenuItem + style={{ fontSize: styles.menuItem.fontSize }} + primaryText={this.props.translate.get(Key.Connect, Deco.CapWords)} + /> </Link>, <a key="subMenuItem-standard-relayer-api" @@ -106,7 +114,10 @@ export class TopBar extends React.Component<TopBarProps, TopBarState> { className="text-decoration-none" href={constants.URL_STANDARD_RELAYER_API_GITHUB} > - <MenuItem style={{ fontSize: styles.menuItem.fontSize }} primaryText="Standard Relayer API" /> + <MenuItem + style={{ fontSize: styles.menuItem.fontSize }} + primaryText={this.props.translate.get(Key.StandardRelayerApi, Deco.CapWords)} + /> </a>, <a key="subMenuItem-github" @@ -122,7 +133,10 @@ export class TopBar extends React.Component<TopBarProps, TopBarState> { className="text-decoration-none" href={`${WebsitePaths.Whitepaper}`} > - <MenuItem style={{ fontSize: styles.menuItem.fontSize }} primaryText="Whitepaper" /> + <MenuItem + style={{ fontSize: styles.menuItem.fontSize }} + primaryText={this.props.translate.get(Key.Whitepaper, Deco.CapWords)} + /> </a>, ]; const bottomBorderStyle = this._shouldDisplayBottomBar() ? styles.bottomBar : {}; @@ -137,7 +151,7 @@ export class TopBar extends React.Component<TopBarProps, TopBarState> { }; const hoverActiveNode = ( <div className="flex relative" style={{ color: menuIconStyle.color }}> - <div style={{ paddingRight: 10 }}>Developers</div> + <div style={{ paddingRight: 10 }}>{this.props.translate.get(Key.Developers, Deco.Cap)}</div> <div className="absolute" style={{ paddingLeft: 3, right: 3, top: -2 }}> <i className="zmdi zmdi-caret-right" style={{ fontSize: 22 }} /> </div> @@ -165,28 +179,28 @@ export class TopBar extends React.Component<TopBarProps, TopBarState> { style={styles.menuItem} /> <TopBarMenuItem - title="Wiki" + title={this.props.translate.get(Key.Wiki, Deco.Cap)} path={`${WebsitePaths.Wiki}`} style={styles.menuItem} isNightVersion={isNightVersion} isExternal={false} /> <TopBarMenuItem - title="Blog" + title={this.props.translate.get(Key.Blog, Deco.Cap)} path={constants.URL_BLOG} style={styles.menuItem} isNightVersion={isNightVersion} isExternal={true} /> <TopBarMenuItem - title="About" + title={this.props.translate.get(Key.About, Deco.Cap)} path={`${WebsitePaths.About}`} style={styles.menuItem} isNightVersion={isNightVersion} isExternal={false} /> <TopBarMenuItem - title="Portal DApp" + title={this.props.translate.get(Key.PortalDApp, Deco.CapWords)} path={`${WebsitePaths.Portal}`} isPrimary={true} style={styles.menuItem} @@ -233,46 +247,54 @@ export class TopBar extends React.Component<TopBarProps, TopBarState> { {this._renderDocsMenu()} {this._renderWiki()} <div className="pl1 py1 mt3" style={{ backgroundColor: colors.lightGrey }}> - Website + {this.props.translate.get(Key.Website, Deco.Cap)} </div> <Link to={WebsitePaths.Home} className="text-decoration-none"> - <MenuItem className="py2">Home</MenuItem> + <MenuItem className="py2">{this.props.translate.get(Key.Home, Deco.Cap)}</MenuItem> </Link> <Link to={`${WebsitePaths.Wiki}`} className="text-decoration-none"> - <MenuItem className="py2">Wiki</MenuItem> + <MenuItem className="py2">{this.props.translate.get(Key.Wiki, Deco.Cap)}</MenuItem> </Link> {!this._isViewing0xjsDocs() && ( <Link to={WebsitePaths.ZeroExJs} className="text-decoration-none"> - <MenuItem className="py2">0x.js Docs</MenuItem> + <MenuItem className="py2">0x.js {this.props.translate.get(Key.Docs, Deco.Cap)}</MenuItem> </Link> )} {!this._isViewingConnectDocs() && ( <Link to={WebsitePaths.Connect} className="text-decoration-none"> - <MenuItem className="py2">0x Connect Docs</MenuItem> + <MenuItem className="py2"> + {this.props.translate.get(Key.Connect, Deco.Cap)}{' '} + {this.props.translate.get(Key.Docs, Deco.Cap)} + </MenuItem> </Link> )} {!this._isViewingSmartContractsDocs() && ( <Link to={WebsitePaths.SmartContracts} className="text-decoration-none"> - <MenuItem className="py2">Smart Contract Docs</MenuItem> + <MenuItem className="py2"> + {this.props.translate.get(Key.SmartContract, Deco.Cap)}{' '} + {this.props.translate.get(Key.Docs, Deco.Cap)} + </MenuItem> </Link> )} {!this._isViewingPortal() && ( <Link to={`${WebsitePaths.Portal}`} className="text-decoration-none"> - <MenuItem className="py2">Portal DApp</MenuItem> + <MenuItem className="py2"> + {this.props.translate.get(Key.PortalDApp, Deco.CapWords)} + </MenuItem> </Link> )} <a className="text-decoration-none" target="_blank" href={`${WebsitePaths.Whitepaper}`}> - <MenuItem className="py2">Whitepaper</MenuItem> + <MenuItem className="py2">{this.props.translate.get(Key.Whitepaper, Deco.Cap)}</MenuItem> </a> <Link to={`${WebsitePaths.About}`} className="text-decoration-none"> - <MenuItem className="py2">About</MenuItem> + <MenuItem className="py2">{this.props.translate.get(Key.About, Deco.Cap)}</MenuItem> </Link> <a className="text-decoration-none" target="_blank" href={constants.URL_BLOG}> - <MenuItem className="py2">Blog</MenuItem> + <MenuItem className="py2">{this.props.translate.get(Key.Blog, Deco.Cap)}</MenuItem> </a> <Link to={`${WebsitePaths.FAQ}`} className="text-decoration-none"> <MenuItem className="py2" onTouchTap={this._onMenuButtonClick.bind(this)}> - FAQ + {this.props.translate.get(Key.Faq, Deco.Cap)} </MenuItem> </Link> </div> @@ -313,7 +335,7 @@ export class TopBar extends React.Component<TopBarProps, TopBarState> { <NestedSidebarMenu topLevelMenu={this.props.menuSubsectionsBySection} menuSubsectionsBySection={this.props.menuSubsectionsBySection} - title="Wiki" + title={this.props.translate.get(Key.Wiki, Deco.Cap)} shouldDisplaySectionHeaders={false} onMenuItemClick={this._onMenuButtonClick.bind(this)} /> @@ -328,7 +350,7 @@ export class TopBar extends React.Component<TopBarProps, TopBarState> { return ( <div className="lg-hide md-hide"> <div className="pl1 py1" style={{ backgroundColor: colors.lightGrey }}> - Portal DApp + {this.props.translate.get(Key.PortalDApp, Deco.CapWords)} </div> <PortalMenu menuItemStyle={{ color: 'black' }} onClick={this._onMenuButtonClick.bind(this)} /> </div> diff --git a/packages/website/ts/components/top_bar/top_bar_menu_item.tsx b/packages/website/ts/components/top_bar/top_bar_menu_item.tsx index 983050abc..e70381456 100644 --- a/packages/website/ts/components/top_bar/top_bar_menu_item.tsx +++ b/packages/website/ts/components/top_bar/top_bar_menu_item.tsx @@ -34,7 +34,7 @@ export class TopBarMenuItem extends React.Component<TopBarMenuItemProps, TopBarM marginTop: 15, paddingLeft: 9, paddingRight: 9, - width: 77, + minWidth: 77, } : {}; const menuItemColor = this.props.isNightVersion ? 'white' : this.props.style.color; diff --git a/packages/website/ts/containers/about.tsx b/packages/website/ts/containers/about.tsx new file mode 100644 index 000000000..ce8fd3afb --- /dev/null +++ b/packages/website/ts/containers/about.tsx @@ -0,0 +1,26 @@ +import * as _ from 'lodash'; +import * as React from 'react'; +import { connect } from 'react-redux'; +import { Dispatch } from 'redux'; +import { About as AboutComponent, AboutProps } from 'ts/pages/about/about'; +import { Dispatcher } from 'ts/redux/dispatcher'; +import { State } from 'ts/redux/reducer'; +import { Translate } from 'ts/utils/translate'; + +interface ConnectedState { + translate: Translate; +} + +interface ConnectedDispatch { + dispatcher: Dispatcher; +} + +const mapStateToProps = (state: State, ownProps: AboutProps): ConnectedState => ({ + translate: state.translate, +}); + +const mapDispatchToProps = (dispatch: Dispatch<State>): ConnectedDispatch => ({ + dispatcher: new Dispatcher(dispatch), +}); + +export const About: React.ComponentClass<AboutProps> = connect(mapStateToProps, mapDispatchToProps)(AboutComponent); diff --git a/packages/website/ts/containers/connect_documentation.tsx b/packages/website/ts/containers/connect_documentation.tsx index 79eafa431..5c5d26b44 100644 --- a/packages/website/ts/containers/connect_documentation.tsx +++ b/packages/website/ts/containers/connect_documentation.tsx @@ -9,6 +9,7 @@ import { State } from 'ts/redux/reducer'; import { DocsInfoConfig, Environments, WebsitePaths } from 'ts/types'; import { configs } from 'ts/utils/configs'; import { constants } from 'ts/utils/constants'; +import { Translate } from 'ts/utils/translate'; import { typeDocUtils } from 'ts/utils/typedoc_utils'; /* tslint:disable:no-var-requires */ @@ -84,6 +85,7 @@ interface ConnectedState { docsVersion: string; availableDocVersions: string[]; docsInfo: DocsInfo; + translate: Translate; } interface ConnectedDispatch { @@ -93,6 +95,7 @@ interface ConnectedDispatch { const mapStateToProps = (state: State, ownProps: DocumentationAllProps): ConnectedState => ({ docsVersion: state.docsVersion, availableDocVersions: state.availableDocVersions, + translate: state.translate, docsInfo, }); diff --git a/packages/website/ts/containers/faq.tsx b/packages/website/ts/containers/faq.tsx new file mode 100644 index 000000000..b539e33c9 --- /dev/null +++ b/packages/website/ts/containers/faq.tsx @@ -0,0 +1,26 @@ +import * as _ from 'lodash'; +import * as React from 'react'; +import { connect } from 'react-redux'; +import { Dispatch } from 'redux'; +import { FAQ as FAQComponent, FAQProps } from 'ts/pages/faq/faq'; +import { Dispatcher } from 'ts/redux/dispatcher'; +import { State } from 'ts/redux/reducer'; +import { Translate } from 'ts/utils/translate'; + +interface ConnectedState { + translate: Translate; +} + +interface ConnectedDispatch { + dispatcher: Dispatcher; +} + +const mapStateToProps = (state: State, ownProps: FAQProps): ConnectedState => ({ + translate: state.translate, +}); + +const mapDispatchToProps = (dispatch: Dispatch<State>): ConnectedDispatch => ({ + dispatcher: new Dispatcher(dispatch), +}); + +export const FAQ: React.ComponentClass<FAQProps> = connect(mapStateToProps, mapDispatchToProps)(FAQComponent); diff --git a/packages/website/ts/containers/landing.tsx b/packages/website/ts/containers/landing.tsx new file mode 100644 index 000000000..a620bb12e --- /dev/null +++ b/packages/website/ts/containers/landing.tsx @@ -0,0 +1,28 @@ +import * as _ from 'lodash'; +import * as React from 'react'; +import { connect } from 'react-redux'; +import { Dispatch } from 'redux'; +import { Landing as LandingComponent, LandingProps } from 'ts/pages/landing/landing'; +import { Dispatcher } from 'ts/redux/dispatcher'; +import { State } from 'ts/redux/reducer'; +import { Translate } from 'ts/utils/translate'; + +interface ConnectedState { + translate: Translate; +} + +interface ConnectedDispatch { + dispatcher: Dispatcher; +} + +const mapStateToProps = (state: State, ownProps: LandingProps): ConnectedState => ({ + translate: state.translate, +}); + +const mapDispatchToProps = (dispatch: Dispatch<State>): ConnectedDispatch => ({ + dispatcher: new Dispatcher(dispatch), +}); + +export const Landing: React.ComponentClass<LandingProps> = connect(mapStateToProps, mapDispatchToProps)( + LandingComponent, +); diff --git a/packages/website/ts/containers/not_found.tsx b/packages/website/ts/containers/not_found.tsx new file mode 100644 index 000000000..dd151e2c8 --- /dev/null +++ b/packages/website/ts/containers/not_found.tsx @@ -0,0 +1,28 @@ +import * as _ from 'lodash'; +import * as React from 'react'; +import { connect } from 'react-redux'; +import { Dispatch } from 'redux'; +import { NotFound as NotFoundComponent, NotFoundProps } from 'ts/pages/not_found'; +import { Dispatcher } from 'ts/redux/dispatcher'; +import { State } from 'ts/redux/reducer'; +import { Translate } from 'ts/utils/translate'; + +interface ConnectedState { + translate: Translate; +} + +interface ConnectedDispatch { + dispatcher: Dispatcher; +} + +const mapStateToProps = (state: State, ownProps: NotFoundProps): ConnectedState => ({ + translate: state.translate, +}); + +const mapDispatchToProps = (dispatch: Dispatch<State>): ConnectedDispatch => ({ + dispatcher: new Dispatcher(dispatch), +}); + +export const NotFound: React.ComponentClass<NotFoundProps> = connect(mapStateToProps, mapDispatchToProps)( + NotFoundComponent, +); diff --git a/packages/website/ts/containers/portal.tsx b/packages/website/ts/containers/portal.tsx index bcca0d70f..befa16bdb 100644 --- a/packages/website/ts/containers/portal.tsx +++ b/packages/website/ts/containers/portal.tsx @@ -8,6 +8,7 @@ import { Dispatcher } from 'ts/redux/dispatcher'; import { State } from 'ts/redux/reducer'; import { BlockchainErrs, HashData, Order, ProviderType, ScreenWidths, Side, TokenByAddress } from 'ts/types'; import { constants } from 'ts/utils/constants'; +import { Translate } from 'ts/utils/translate'; interface ConnectedState { blockchainErr: BlockchainErrs; @@ -26,6 +27,7 @@ interface ConnectedState { userAddress: string; userSuppliedOrderCache: Order; flashMessage?: string | React.ReactNode; + translate: Translate; } interface ConnectedDispatch { @@ -73,6 +75,7 @@ const mapStateToProps = (state: State, ownProps: PortalComponentAllProps): Conne userEtherBalance: state.userEtherBalance, userSuppliedOrderCache: state.userSuppliedOrderCache, flashMessage: state.flashMessage, + translate: state.translate, }; }; diff --git a/packages/website/ts/containers/smart_contracts_documentation.tsx b/packages/website/ts/containers/smart_contracts_documentation.tsx index 8be33b546..c4731f929 100644 --- a/packages/website/ts/containers/smart_contracts_documentation.tsx +++ b/packages/website/ts/containers/smart_contracts_documentation.tsx @@ -8,6 +8,7 @@ import { Dispatcher } from 'ts/redux/dispatcher'; import { State } from 'ts/redux/reducer'; import { DocsInfoConfig, SmartContractDocSections as Sections, WebsitePaths } from 'ts/types'; import { doxityUtils } from 'ts/utils/doxity_utils'; +import { Translate } from 'ts/utils/translate'; /* tslint:disable:no-var-requires */ const IntroMarkdown = require('md/docs/smart_contracts/introduction'); @@ -40,6 +41,7 @@ const docsInfo = new DocsInfo(docsInfoConfig); interface ConnectedState { docsVersion: string; availableDocVersions: string[]; + translate: Translate; } interface ConnectedDispatch { @@ -50,6 +52,7 @@ interface ConnectedDispatch { const mapStateToProps = (state: State, ownProps: DocumentationAllProps): ConnectedState => ({ docsVersion: state.docsVersion, availableDocVersions: state.availableDocVersions, + translate: state.translate, }); const mapDispatchToProps = (dispatch: Dispatch<State>): ConnectedDispatch => ({ diff --git a/packages/website/ts/containers/wiki.tsx b/packages/website/ts/containers/wiki.tsx new file mode 100644 index 000000000..2cb87d0a1 --- /dev/null +++ b/packages/website/ts/containers/wiki.tsx @@ -0,0 +1,26 @@ +import * as _ from 'lodash'; +import * as React from 'react'; +import { connect } from 'react-redux'; +import { Dispatch } from 'redux'; +import { Wiki as WikiComponent, WikiProps } from 'ts/pages/wiki/wiki'; +import { Dispatcher } from 'ts/redux/dispatcher'; +import { State } from 'ts/redux/reducer'; +import { Translate } from 'ts/utils/translate'; + +interface ConnectedState { + translate: Translate; +} + +interface ConnectedDispatch { + dispatcher: Dispatcher; +} + +const mapStateToProps = (state: State, ownProps: WikiProps): ConnectedState => ({ + translate: state.translate, +}); + +const mapDispatchToProps = (dispatch: Dispatch<State>): ConnectedDispatch => ({ + dispatcher: new Dispatcher(dispatch), +}); + +export const Wiki: React.ComponentClass<WikiProps> = connect(mapStateToProps, mapDispatchToProps)(WikiComponent); diff --git a/packages/website/ts/containers/zero_ex_js_documentation.tsx b/packages/website/ts/containers/zero_ex_js_documentation.tsx index eee2c7cc8..a32a87f7f 100644 --- a/packages/website/ts/containers/zero_ex_js_documentation.tsx +++ b/packages/website/ts/containers/zero_ex_js_documentation.tsx @@ -9,6 +9,7 @@ import { State } from 'ts/redux/reducer'; import { DocsInfoConfig, Environments, WebsitePaths } from 'ts/types'; import { configs } from 'ts/utils/configs'; import { constants } from 'ts/utils/constants'; +import { Translate } from 'ts/utils/translate'; import { typeDocUtils } from 'ts/utils/typedoc_utils'; /* tslint:disable:no-var-requires */ @@ -154,6 +155,7 @@ interface ConnectedState { docsVersion: string; availableDocVersions: string[]; docsInfo: DocsInfo; + translate: Translate; } interface ConnectedDispatch { @@ -164,6 +166,7 @@ const mapStateToProps = (state: State, ownProps: DocumentationAllProps): Connect docsVersion: state.docsVersion, availableDocVersions: state.availableDocVersions, docsInfo, + translate: state.translate, }); const mapDispatchToProps = (dispatch: Dispatch<State>): ConnectedDispatch => ({ diff --git a/packages/website/ts/index.tsx b/packages/website/ts/index.tsx index bc4e0b472..c0539c6d0 100644 --- a/packages/website/ts/index.tsx +++ b/packages/website/ts/index.tsx @@ -7,14 +7,14 @@ import { Provider } from 'react-redux'; import { BrowserRouter as Router, Redirect, Route, Switch } from 'react-router-dom'; import * as injectTapEventPlugin from 'react-tap-event-plugin'; import { createStore, Store as ReduxStore } from 'redux'; +import { About } from 'ts/containers/about'; +import { FAQ } from 'ts/containers/faq'; +import { Landing } from 'ts/containers/landing'; +import { NotFound } from 'ts/containers/not_found'; +import { Wiki } from 'ts/containers/wiki'; import { createLazyComponent } from 'ts/lazy_component'; import { trackedTokenStorage } from 'ts/local_storage/tracked_token_storage'; import { tradeHistoryStorage } from 'ts/local_storage/trade_history_storage'; -import { About } from 'ts/pages/about/about'; -import { FAQ } from 'ts/pages/faq/faq'; -import { Landing } from 'ts/pages/landing/landing'; -import { NotFound } from 'ts/pages/not_found'; -import { Wiki } from 'ts/pages/wiki/wiki'; import { reducer, State } from 'ts/redux/reducer'; import { WebsitePaths } from 'ts/types'; import { muiTheme } from 'ts/utils/mui_theme'; diff --git a/packages/website/ts/pages/about/about.tsx b/packages/website/ts/pages/about/about.tsx index 0889e79f3..b99dc34ab 100644 --- a/packages/website/ts/pages/about/about.tsx +++ b/packages/website/ts/pages/about/about.tsx @@ -4,9 +4,11 @@ import * as DocumentTitle from 'react-document-title'; import { Footer } from 'ts/components/footer'; import { TopBar } from 'ts/components/top_bar/top_bar'; import { Profile } from 'ts/pages/about/profile'; +import { Dispatcher } from 'ts/redux/dispatcher'; import { ProfileInfo, Styles } from 'ts/types'; import { colors } from 'ts/utils/colors'; import { constants } from 'ts/utils/constants'; +import { Translate } from 'ts/utils/translate'; import { utils } from 'ts/utils/utils'; const teamRow1: ProfileInfo[] = [ @@ -143,6 +145,8 @@ const advisors: ProfileInfo[] = [ export interface AboutProps { source: string; location: Location; + translate: Translate; + dispatcher: Dispatcher; } interface AboutState {} @@ -174,6 +178,7 @@ export class About extends React.Component<AboutProps, AboutState> { blockchainIsLoaded={false} location={this.props.location} style={{ backgroundColor: colors.lightestGrey }} + translate={this.props.translate} /> <div id="about" className="mx-auto max-width-4 py4" style={{ color: colors.grey800 }}> <div className="mx-auto pb4 sm-px3" style={{ maxWidth: 435 }}> @@ -230,7 +235,7 @@ export class About extends React.Component<AboutProps, AboutState> { </div> </div> </div> - <Footer /> + <Footer translate={this.props.translate} dispatcher={this.props.dispatcher} /> </div> ); } diff --git a/packages/website/ts/pages/documentation/documentation.tsx b/packages/website/ts/pages/documentation/documentation.tsx index da3728a60..285471166 100644 --- a/packages/website/ts/pages/documentation/documentation.tsx +++ b/packages/website/ts/pages/documentation/documentation.tsx @@ -35,6 +35,7 @@ import { colors } from 'ts/utils/colors'; import { configs } from 'ts/utils/configs'; import { constants } from 'ts/utils/constants'; import { docUtils } from 'ts/utils/doc_utils'; +import { Translate } from 'ts/utils/translate'; import { utils } from 'ts/utils/utils'; const TOP_BAR_HEIGHT = 60; @@ -54,6 +55,7 @@ export interface DocumentationAllProps { docsVersion: string; availableDocVersions: string[]; docsInfo: DocsInfo; + translate: Translate; } interface DocumentationState { @@ -114,6 +116,7 @@ export class Documentation extends React.Component<DocumentationAllProps, Docume menu={this.props.docsInfo.getMenu(this.props.docsVersion)} menuSubsectionsBySection={menuSubsectionsBySection} docsInfo={this.props.docsInfo} + translate={this.props.translate} /> {_.isUndefined(this.state.docAgnosticFormat) ? ( <div className="col col-12" style={styles.mainContainers}> diff --git a/packages/website/ts/pages/faq/faq.tsx b/packages/website/ts/pages/faq/faq.tsx index 0a7eecc2d..34175abdc 100644 --- a/packages/website/ts/pages/faq/faq.tsx +++ b/packages/website/ts/pages/faq/faq.tsx @@ -4,14 +4,18 @@ import * as DocumentTitle from 'react-document-title'; import { Footer } from 'ts/components/footer'; import { TopBar } from 'ts/components/top_bar/top_bar'; import { Question } from 'ts/pages/faq/question'; +import { Dispatcher } from 'ts/redux/dispatcher'; import { FAQQuestion, FAQSection, Styles, WebsitePaths } from 'ts/types'; import { colors } from 'ts/utils/colors'; import { configs } from 'ts/utils/configs'; import { constants } from 'ts/utils/constants'; +import { Translate } from 'ts/utils/translate'; export interface FAQProps { source: string; location: Location; + translate: Translate; + dispatcher: Dispatcher; } interface FAQState {} @@ -407,14 +411,14 @@ export class FAQ extends React.Component<FAQProps, FAQState> { return ( <div> <DocumentTitle title="0x FAQ" /> - <TopBar blockchainIsLoaded={false} location={this.props.location} /> + <TopBar blockchainIsLoaded={false} location={this.props.location} translate={this.props.translate} /> <div id="faq" className="mx-auto max-width-4 pt4" style={{ color: colors.grey800 }}> <h1 className="center" style={{ ...styles.thin }}> 0x FAQ </h1> <div className="sm-px2 md-px2 lg-px0 pb4">{this._renderSections()}</div> </div> - <Footer /> + <Footer translate={this.props.translate} dispatcher={this.props.dispatcher} /> </div> ); } diff --git a/packages/website/ts/pages/landing/landing.tsx b/packages/website/ts/pages/landing/landing.tsx index d4c934459..044f0b41f 100644 --- a/packages/website/ts/pages/landing/landing.tsx +++ b/packages/website/ts/pages/landing/landing.tsx @@ -5,9 +5,11 @@ import DocumentTitle = require('react-document-title'); import { Link } from 'react-router-dom'; import { Footer } from 'ts/components/footer'; import { TopBar } from 'ts/components/top_bar/top_bar'; -import { ScreenWidths, WebsitePaths } from 'ts/types'; +import { Dispatcher } from 'ts/redux/dispatcher'; +import { Deco, Key, Language, ScreenWidths, WebsitePaths } from 'ts/types'; import { colors } from 'ts/utils/colors'; import { constants } from 'ts/utils/constants'; +import { Translate } from 'ts/utils/translate'; import { utils } from 'ts/utils/utils'; interface BoxContent { @@ -36,35 +38,6 @@ interface Project { const THROTTLE_TIMEOUT = 100; -const boxContents: BoxContent[] = [ - { - title: 'Trustless exchange', - description: - "Built on Ethereum's distributed network with no centralized \ - point of failure and no down time, each trade is settled atomically \ - and without counterparty risk.", - imageUrl: '/images/landing/distributed_network.png', - classNames: '', - }, - { - title: 'Shared liquidity', - description: - 'By sharing a standard API, relayers can easily aggregate liquidity pools, \ - creating network effects around liquidity that compound as more relayers come online.', - imageUrl: '/images/landing/liquidity.png', - classNames: 'mx-auto', - }, - { - title: 'Open source', - description: - '0x is open source, permissionless and free to use. Trade directly with a known \ - counterparty for free or pay a relayer some ZRX tokens to access their liquidity \ - pool.', - imageUrl: '/images/landing/open_source.png', - classNames: 'right', - }, -]; - const relayersAndDappProjects: Project[] = [ { logoFileName: 'ethfinex.png', @@ -185,6 +158,8 @@ const relayerProjects: Project[] = [ export interface LandingProps { location: Location; + translate: Translate; + dispatcher: Dispatcher; } interface LandingState { @@ -216,17 +191,28 @@ export class Landing extends React.Component<LandingProps, LandingState> { location={this.props.location} isNightVersion={true} style={{ backgroundColor: colors.heroGrey, position: 'relative' }} + translate={this.props.translate} /> {this._renderHero()} - {this._renderProjects(relayersAndDappProjects, 'Projects building on 0x', colors.projectsGrey, false)} + {this._renderProjects( + relayersAndDappProjects, + this.props.translate.get(Key.ProjectsHeader, Deco.Upper), + colors.projectsGrey, + false, + )} {this._renderTokenizationSection()} {this._renderProtocolSection()} - {this._renderProjects(relayerProjects, 'Relayers building on 0x', colors.heroGrey, true)} + {this._renderProjects( + relayerProjects, + this.props.translate.get(Key.RelayersHeader, Deco.Upper), + colors.heroGrey, + true, + )} {this._renderInfoBoxes()} {this._renderBuildingBlocksSection()} {this._renderUseCases()} {this._renderCallToAction()} - <Footer /> + <Footer translate={this.props.translate} dispatcher={this.props.dispatcher} /> </div> ); } @@ -243,7 +229,7 @@ export class Landing extends React.Component<LandingProps, LandingState> { lineHeight: '33px', height: 38, }; - const left = 'col lg-col-7 md-col-7 col-12 lg-pt4 md-pt4 sm-pt0 mt1 lg-pl4 md-pl4 sm-pl0 sm-px3 sm-center'; + const left = 'col lg-col-7 md-col-7 col-12 lg-pl4 md-pl4 sm-pl0 sm-px3 sm-center'; return ( <div className="clearfix py4" style={{ backgroundColor: colors.heroGrey }}> <div className="mx-auto max-width-4 clearfix"> @@ -251,8 +237,14 @@ export class Landing extends React.Component<LandingProps, LandingState> { <div className="col lg-col-5 md-col-5 col-12 sm-center"> <img src="/images/landing/hero_chip_image.png" height={isSmallScreen ? 300 : 395} /> </div> - <div className={left} style={{ color: colors.white }}> - <div style={{ paddingLeft: isSmallScreen ? 0 : 12 }}> + <div className={left} style={{ color: colors.white, height: 390, lineHeight: '390px' }}> + <div + className="inline-block lg-align-middle md-align-middle sm-align-top" + style={{ + paddingLeft: isSmallScreen ? 0 : 12, + lineHeight: '36px', + }} + > <div className="sm-pb2" style={{ @@ -260,7 +252,7 @@ export class Landing extends React.Component<LandingProps, LandingState> { fontSize: isSmallScreen ? 26 : 34, }} > - Powering decentralized exchange + {this.props.translate.get(Key.TopHeader, Deco.Cap)} </div> <div className="pt2 h5 sm-mx-auto" @@ -271,17 +263,16 @@ export class Landing extends React.Component<LandingProps, LandingState> { fontWeight: 300, }} > - 0x is an open, permissionless protocol allowing for ERC20 tokens to be traded on the - Ethereum blockchain. + {this.props.translate.get(Key.TopTagline)} </div> - <div className="pt3 clearfix sm-mx-auto" style={{ maxWidth: 342 }}> + <div className="pt3 clearfix sm-mx-auto" style={{ maxWidth: 389 }}> <div className="lg-pr2 md-pr2 col col-6 sm-center"> <Link to={WebsitePaths.ZeroExJs} className="text-decoration-none"> <RaisedButton style={{ borderRadius: 6, minWidth: 157.36 }} buttonStyle={{ borderRadius: 6 }} labelStyle={buttonLabelStyle} - label="Build on 0x" + label={this.props.translate.get(Key.BuildCallToAction, Deco.Cap)} onClick={_.noop} /> </Link> @@ -298,7 +289,7 @@ export class Landing extends React.Component<LandingProps, LandingState> { labelColor="white" backgroundColor={colors.heroGrey} labelStyle={buttonLabelStyle} - label="Join the community" + label={this.props.translate.get(Key.CommunityCallToAction, Deco.Cap)} onClick={_.noop} /> </a> @@ -313,7 +304,6 @@ export class Landing extends React.Component<LandingProps, LandingState> { } private _renderProjects(projects: Project[], title: string, backgroundColor: string, isTitleCenter: boolean) { const isSmallScreen = this.state.screenWidth === ScreenWidths.Sm; - const isMediumScreen = this.state.screenWidth === ScreenWidths.Md; const projectList = _.map(projects, (project: Project, i: number) => { const isRelayersOnly = projects.length === 12; let colWidth: number; @@ -346,7 +336,6 @@ export class Landing extends React.Component<LandingProps, LandingState> { const titleStyle: React.CSSProperties = { fontFamily: 'Roboto Mono', color: colors.grey, - textTransform: 'uppercase', fontWeight: 300, letterSpacing: 3, }; @@ -366,13 +355,13 @@ export class Landing extends React.Component<LandingProps, LandingState> { fontSize: 14, }} > - view the{' '} + {this.props.translate.get(Key.FullListPrompt)}{' '} <Link to={`${WebsitePaths.Wiki}#List-of-Projects-Using-0x-Protocol`} className="text-decoration-none underline" style={{ color: colors.landingLinkGrey }} > - full list + {this.props.translate.get(Key.FullListLink)} </Link> </div> </div> @@ -385,33 +374,22 @@ export class Landing extends React.Component<LandingProps, LandingState> { <div className="clearfix lg-py4 md-py4 sm-pb4 sm-pt2" style={{ backgroundColor: colors.grey100 }}> <div className="mx-auto max-width-4 py4 clearfix"> {isSmallScreen && this._renderTokenCloud()} - <div className="col lg-col-6 md-col-6 col-12" style={{ color: colors.darkestGrey }}> - <div className="mx-auto" style={{ maxWidth: 385, paddingTop: 7 }}> + <div + className="col lg-col-6 md-col-6 col-12 center" + style={{ color: colors.darkestGrey, height: 364, lineHeight: '364px' }} + > + <div + className="mx-auto inline-block lg-align-middle md-align-middle sm-align-top" + style={{ maxWidth: 385, lineHeight: '44px', textAlign: 'left' }} + > <div className="lg-h1 md-h1 sm-h2 sm-center sm-pt3" style={{ fontFamily: 'Roboto Mono' }}> - The world's value is becoming tokenized + {this.props.translate.get(Key.TokenizedSectionHeader, Deco.Cap)} </div> <div className="pb2 lg-pt2 md-pt2 sm-pt3 sm-px3 h5 sm-center" - style={{ fontFamily: 'Roboto Mono', lineHeight: 1.7 }} + style={{ fontFamily: 'Roboto Mono', lineHeight: 1.7, maxWidth: 370 }} > - {isSmallScreen ? ( - <span> - The Ethereum blockchain is an open, borderless financial system that represents - a wide variety of assets as cryptographic tokens. In the future, most digital - assets and goods will be tokenized. - </span> - ) : ( - <div> - <div> - The Ethereum blockchain is an open, borderless financial system that - represents - </div> - <div> - a wide variety of assets as cryptographic tokens. In the future, most - digital assets and goods will be tokenized. - </div> - </div> - )} + {this.props.translate.get(Key.TokenizedSectionDescription, Deco.Cap)} </div> <div className="flex pt1 sm-px3">{this._renderAssetTypes()}</div> </div> @@ -430,28 +408,36 @@ export class Landing extends React.Component<LandingProps, LandingState> { <img src="/images/landing/relayer_diagram.png" height={isSmallScreen ? 326 : 426} /> </div> <div - className="col lg-col-6 md-col-6 col-12 lg-pr3 md-pr3 sm-mx-auto lg-pt4 md-pt4 lg-mt3 md-mt3" + className="col lg-col-6 md-col-6 col-12 lg-pr3 md-pr3 sm-mx-auto" style={{ color: colors.beigeWhite, maxWidth: isSmallScreen ? 'none' : 445, + height: 430, + lineHeight: '430px', }} > - <div className="lg-h1 md-h1 sm-h2 pb1 sm-pt3 sm-center" style={{ fontFamily: 'Roboto Mono' }}> - <div>Off-chain order relay</div> - <div>On-chain settlement</div> - </div> <div - className="pb2 pt2 h5 sm-center sm-px3 sm-mx-auto" - style={{ - fontFamily: 'Roboto Mono', - lineHeight: 1.7, - fontWeight: 300, - maxWidth: 445, - }} + className="inline-block lg-align-middle md-align-middle sm-align-top" + style={{ lineHeight: '43px' }} > - In 0x protocol, orders are transported off-chain, massively reducing gas costs and - eliminating blockchain bloat. Relayers help broadcast orders and collect a fee each time - they facilitate a trade. Anyone can build a relayer. + <div + className="lg-h1 md-h1 sm-h2 pb1 sm-pt3 sm-center" + style={{ fontFamily: 'Roboto Mono' }} + > + <div>{this.props.translate.get(Key.OffChainOrderRelay, Deco.Cap)}</div> + <div> {this.props.translate.get(Key.OonChainSettlement, Deco.Cap)}</div> + </div> + <div + className="pb2 pt2 h5 sm-center sm-px3 sm-mx-auto" + style={{ + fontFamily: 'Roboto Mono', + lineHeight: 1.7, + fontWeight: 300, + maxWidth: 445, + }} + > + {this.props.translate.get(Key.OffChainOnChainDescription, Deco.Cap)} + </div> </div> </div> </div> @@ -485,15 +471,13 @@ export class Landing extends React.Component<LandingProps, LandingState> { className="pb1 lg-pt4 md-pt4 sm-pt3 lg-h1 md-h1 sm-h2 sm-px3 sm-center" style={{ fontFamily: 'Roboto Mono' }} > - A building block for dApps + {this.props.translate.get(Key.BuildingBlockSectionHeader, Deco.Cap)} </div> <div className="pb3 pt2 sm-mx-auto sm-center" style={descriptionStyle}> - 0x protocol is a pluggable building block for dApps that require exchange functionality. - Join the many developers that are already using 0x in their web applications and smart - contracts. + {this.props.translate.get(Key.BuildingBlockSectionDescription, Deco.Cap)} </div> <div className="sm-mx-auto sm-center" style={callToActionStyle}> - Learn how in our{' '} + {this.props.translate.get(Key.DevToolsPrompt, Deco.Cap)}{' '} <Link to={WebsitePaths.ZeroExJs} className="text-decoration-none underline" @@ -501,15 +485,15 @@ export class Landing extends React.Component<LandingProps, LandingState> { > 0x.js </Link>{' '} - and{' '} + {this.props.translate.get(Key.And)}{' '} <Link to={WebsitePaths.SmartContracts} className="text-decoration-none underline" style={{ color: colors.beigeWhite, fontFamily: 'Roboto Mono' }} > - smart contract + {this.props.translate.get(Key.SmartContract)} </Link>{' '} - docs + {this.props.translate.get(Key.Docs)} </div> </div> {!isSmallScreen && this._renderBlockChipImage()} @@ -537,11 +521,11 @@ export class Landing extends React.Component<LandingProps, LandingState> { const isSmallScreen = this.state.screenWidth === ScreenWidths.Sm; const assetTypes: AssetType[] = [ { - title: 'Currency', + title: this.props.translate.get(Key.Currency, Deco.Cap), imageUrl: '/images/landing/currency.png', }, { - title: 'Traditional assets', + title: this.props.translate.get(Key.TraditionalAssets, Deco.Cap), imageUrl: '/images/landing/stocks.png', style: { paddingLeft: isSmallScreen ? 41 : 56, @@ -549,7 +533,7 @@ export class Landing extends React.Component<LandingProps, LandingState> { }, }, { - title: 'Digital goods', + title: this.props.translate.get(Key.DigitalGoods, Deco.Cap), imageUrl: '/images/landing/digital_goods.png', }, ]; @@ -566,6 +550,7 @@ export class Landing extends React.Component<LandingProps, LandingState> { fontSize: 13.5, fontWeight: 400, color: colors.darkestGrey, + lineHeight: 1.4, }} > {assetType.title} @@ -578,12 +563,32 @@ export class Landing extends React.Component<LandingProps, LandingState> { private _renderInfoBoxes() { const isSmallScreen = this.state.screenWidth === ScreenWidths.Sm; const boxStyle: React.CSSProperties = { - maxWidth: 252, - height: 386, + maxWidth: 253, + height: 402, backgroundColor: colors.grey50, borderRadius: 5, padding: '10px 24px 24px', }; + const boxContents: BoxContent[] = [ + { + title: this.props.translate.get(Key.BenefitOneTitle, Deco.Cap), + description: this.props.translate.get(Key.BenefitOneDescription, Deco.Cap), + imageUrl: '/images/landing/distributed_network.png', + classNames: '', + }, + { + title: this.props.translate.get(Key.BenefitTwoTitle, Deco.Cap), + description: this.props.translate.get(Key.BenefitTwoDescription, Deco.Cap), + imageUrl: '/images/landing/liquidity.png', + classNames: 'mx-auto', + }, + { + title: this.props.translate.get(Key.BenefitThreeTitle, Deco.Cap), + description: this.props.translate.get(Key.BenefitThreeDescription, Deco.Cap), + imageUrl: '/images/landing/open_source.png', + classNames: 'right', + }, + ]; const boxes = _.map(boxContents, (boxContent: BoxContent) => { return ( <div key={`box-${boxContent.title}`} className="col lg-col-4 md-col-4 col-12 sm-pb4"> @@ -604,14 +609,13 @@ export class Landing extends React.Component<LandingProps, LandingState> { const titleStyle: React.CSSProperties = { fontFamily: 'Roboto Mono', color: colors.grey, - textTransform: 'uppercase', fontWeight: 300, letterSpacing: 3, }; return ( <div className="clearfix" style={{ backgroundColor: colors.heroGrey }}> <div className="center pb3 pt4" style={titleStyle}> - Benefits of 0x + {this.props.translate.get(Key.BenefitsHeader, Deco.Upper)} </div> <div className="mx-auto pb4 sm-mt2 clearfix" style={{ maxWidth: '60em' }}> {boxes} @@ -625,41 +629,29 @@ export class Landing extends React.Component<LandingProps, LandingState> { const useCases: UseCase[] = [ { imageUrl: '/images/landing/governance_icon.png', - type: 'Decentralized governance', - description: - 'Decentralized organizations use tokens to represent ownership and \ - guide their governance logic. 0x allows decentralized organizations \ - to seamlessly and safely trade ownership for startup capital.', + type: this.props.translate.get(Key.DecentralizedGovernance, Deco.Upper), + description: this.props.translate.get(Key.DecentralizedGovernanceDescription, Deco.Cap), projectIconUrls: ['/images/landing/aragon.png'], classNames: 'lg-px2 md-px2', }, { imageUrl: '/images/landing/prediction_market_icon.png', - type: 'Prediction markets', - description: - 'Decentralized prediction market platforms generate sets of tokens that \ - represent a financial stake in the outcomes of real-world events. 0x allows \ - these tokens to be instantly tradable.', + type: this.props.translate.get(Key.PredictionMarkets, Deco.Upper), + description: this.props.translate.get(Key.PredictionMarketsDescription, Deco.Cap), projectIconUrls: ['/images/landing/augur.png'], classNames: 'lg-px2 md-px2', }, { imageUrl: '/images/landing/stable_tokens_icon.png', - type: 'Stable tokens', - description: - 'Novel economic constructs such as stable coins require efficient, liquid \ - markets to succeed. 0x will facilitate the underlying economic mechanisms \ - that allow these tokens to remain stable.', + type: this.props.translate.get(Key.StableTokens, Deco.Upper), + description: this.props.translate.get(Key.StableTokensDescription, Deco.Cap), projectIconUrls: ['/images/landing/maker.png'], classNames: 'lg-px2 md-px2', }, { imageUrl: '/images/landing/loans_icon.png', - type: 'Decentralized loans', - description: - 'Efficient lending requires liquid markets where investors can buy and re-sell loans. \ - 0x enables an ecosystem of lenders to self-organize and efficiently determine \ - market prices for all outstanding loans.', + type: this.props.translate.get(Key.DecentralizedLoans, Deco.Upper), + description: this.props.translate.get(Key.DecentralizedLoansDescription, Deco.Cap), projectIconUrls: ['/images/landing/dharma.png', '/images/landing/lendroid.png'], classNames: 'lg-pr2 md-pr2 lg-col-6 md-col-6', style: { @@ -670,11 +662,8 @@ export class Landing extends React.Component<LandingProps, LandingState> { }, { imageUrl: '/images/landing/fund_management_icon.png', - type: 'Fund management', - description: - 'Decentralized fund management limits fund managers to investing in pre-agreed \ - upon asset classes. Embedding 0x into fund management smart contracts enables \ - them to enforce these security constraints.', + type: this.props.translate.get(Key.FundManagement, Deco.Upper), + description: this.props.translate.get(Key.FundManagementDescription, Deco.Cap), projectIconUrls: ['/images/landing/melonport.png'], classNames: 'lg-pl2 md-pl2 lg-col-6 md-col-6', style: { width: 291, marginTop: !isSmallScreen ? 38 : 0 }, @@ -685,7 +674,7 @@ export class Landing extends React.Component<LandingProps, LandingState> { const style = _.isUndefined(useCase.style) || isSmallScreen ? {} : useCase.style; const useCaseBoxStyle = { color: colors.grey, - border: '1px solid #565656', + border: `1px solid ${colors.grey750}`, borderRadius: 4, maxWidth: isSmallScreen ? 375 : 'none', ...style, @@ -741,38 +730,39 @@ export class Landing extends React.Component<LandingProps, LandingState> { }; const lightButtonStyle: React.CSSProperties = { borderRadius: 6, - border: '1px solid #a0a0a0', + border: `1px solid ${colors.grey500}`, lineHeight: '33px', height: 49, }; const callToActionClassNames = - 'col lg-col-8 md-col-8 col-12 lg-pr3 md-pr3 \ - lg-right-align md-right-align sm-center sm-px3 h4'; + 'lg-pr3 md-pr3 lg-right-align md-right-align sm-center sm-px3 h4 lg-table-cell md-table-cell'; return ( <div className="clearfix pb4" style={{ backgroundColor: colors.heroGrey }}> - <div className="mx-auto max-width-4 pb4 mb3 clearfix"> - <div - className={callToActionClassNames} - style={{ - fontFamily: 'Roboto Mono', - color: colors.white, - lineHeight: isSmallScreen ? 1.7 : 3, - }} - > - Get started on building the decentralized future - </div> - <div className="col lg-col-4 md-col-4 col-12 sm-center sm-pt2"> - <Link to={WebsitePaths.ZeroExJs} className="text-decoration-none"> - <RaisedButton - style={{ borderRadius: 6, minWidth: 150 }} - buttonStyle={lightButtonStyle} - labelColor={colors.white} - backgroundColor={colors.heroGrey} - labelStyle={buttonLabelStyle} - label="Build on 0x" - onClick={_.noop} - /> - </Link> + <div className="mx-auto max-width-4 pb4 mb3 clearfix center"> + <div className="center inline-block" style={{ textAlign: 'left' }}> + <div + className={callToActionClassNames} + style={{ + fontFamily: 'Roboto Mono', + color: colors.white, + lineHeight: isSmallScreen ? 1.7 : 3, + }} + > + {this.props.translate.get(Key.FinalCallToAction, Deco.Cap)} + </div> + <div className="sm-center sm-pt2 lg-table-cell md-table-cell"> + <Link to={WebsitePaths.ZeroExJs} className="text-decoration-none"> + <RaisedButton + style={{ borderRadius: 6, minWidth: 150 }} + buttonStyle={lightButtonStyle} + labelColor={colors.white} + backgroundColor={colors.heroGrey} + labelStyle={buttonLabelStyle} + label={this.props.translate.get(Key.BuildCallToAction, Deco.Cap)} + onClick={_.noop} + /> + </Link> + </div> </div> </div> </div> @@ -786,4 +776,7 @@ export class Landing extends React.Component<LandingProps, LandingState> { }); } } + private _onLanguageSelected(language: Language) { + this.props.dispatcher.updateSelectedLanguage(language); + } } // tslint:disable:max-file-line-count diff --git a/packages/website/ts/pages/not_found.tsx b/packages/website/ts/pages/not_found.tsx index 0a6ec071c..ad37f6242 100644 --- a/packages/website/ts/pages/not_found.tsx +++ b/packages/website/ts/pages/not_found.tsx @@ -2,10 +2,14 @@ import * as _ from 'lodash'; import * as React from 'react'; import { Footer } from 'ts/components/footer'; import { TopBar } from 'ts/components/top_bar/top_bar'; +import { Dispatcher } from 'ts/redux/dispatcher'; import { Styles } from 'ts/types'; +import { Translate } from 'ts/utils/translate'; export interface NotFoundProps { location: Location; + translate: Translate; + dispatcher: Dispatcher; } interface NotFoundState {} @@ -20,7 +24,7 @@ export class NotFound extends React.Component<NotFoundProps, NotFoundState> { public render() { return ( <div> - <TopBar blockchainIsLoaded={false} location={this.props.location} /> + <TopBar blockchainIsLoaded={false} location={this.props.location} translate={this.props.translate} /> <div className="mx-auto max-width-4 py4"> <div className="center py4"> <div className="py4"> @@ -35,7 +39,7 @@ export class NotFound extends React.Component<NotFoundProps, NotFoundState> { </div> </div> </div> - <Footer /> + <Footer translate={this.props.translate} dispatcher={this.props.dispatcher} /> </div> ); } diff --git a/packages/website/ts/pages/wiki/wiki.tsx b/packages/website/ts/pages/wiki/wiki.tsx index bbbda6eee..56c3be0fe 100644 --- a/packages/website/ts/pages/wiki/wiki.tsx +++ b/packages/website/ts/pages/wiki/wiki.tsx @@ -8,10 +8,12 @@ import { TopBar } from 'ts/components/top_bar/top_bar'; import { MarkdownSection } from 'ts/pages/shared/markdown_section'; import { NestedSidebarMenu } from 'ts/pages/shared/nested_sidebar_menu'; import { SectionHeader } from 'ts/pages/shared/section_header'; +import { Dispatcher } from 'ts/redux/dispatcher'; import { Article, ArticlesBySection, HeaderSizes, Styles, WebsitePaths } from 'ts/types'; import { colors } from 'ts/utils/colors'; import { configs } from 'ts/utils/configs'; import { constants } from 'ts/utils/constants'; +import { Translate } from 'ts/utils/translate'; import { utils } from 'ts/utils/utils'; const TOP_BAR_HEIGHT = 60; @@ -20,6 +22,8 @@ const WIKI_NOT_READY_BACKOUT_TIMEOUT_MS = 5000; export interface WikiProps { source: string; location: Location; + dispatcher: Dispatcher; + translate: Translate; } interface WikiState { @@ -79,6 +83,7 @@ export class Wiki extends React.Component<WikiProps, WikiState> { blockchainIsLoaded={false} location={this.props.location} menuSubsectionsBySection={menuSubsectionsBySection} + translate={this.props.translate} /> {_.isUndefined(this.state.articlesBySection) ? ( <div className="col col-12" style={mainContainersStyle}> diff --git a/packages/website/ts/redux/dispatcher.ts b/packages/website/ts/redux/dispatcher.ts index 925f2aa29..5c40ded2c 100644 --- a/packages/website/ts/redux/dispatcher.ts +++ b/packages/website/ts/redux/dispatcher.ts @@ -6,6 +6,7 @@ import { ActionTypes, AssetToken, BlockchainErrs, + Language, Order, ProviderType, ScreenWidths, @@ -211,4 +212,10 @@ export class Dispatcher { data: injectedProviderName, }); } + public updateSelectedLanguage(language: Language) { + this._dispatch({ + type: ActionTypes.UpdateSelectedLanguage, + data: language, + }); + } } diff --git a/packages/website/ts/redux/reducer.ts b/packages/website/ts/redux/reducer.ts index c2a21dc07..1f489db85 100644 --- a/packages/website/ts/redux/reducer.ts +++ b/packages/website/ts/redux/reducer.ts @@ -13,6 +13,7 @@ import { SideToAssetToken, TokenByAddress, } from 'ts/types'; +import { Translate } from 'ts/utils/translate'; import { utils } from 'ts/utils/utils'; // Instead of defaulting the docs version to an empty string, we pre-populate it with @@ -49,6 +50,7 @@ export interface State { flashMessage: string | React.ReactNode; providerType: ProviderType; injectedProviderName: string; + translate: Translate; } const INITIAL_STATE: State = { @@ -86,13 +88,17 @@ const INITIAL_STATE: State = { flashMessage: undefined, providerType: ProviderType.Injected, injectedProviderName: '', + translate: new Translate(), }; export function reducer(state: State = INITIAL_STATE, action: Action) { switch (action.type) { // Portal case ActionTypes.ResetState: - return INITIAL_STATE; + return { + ...INITIAL_STATE, + translate: state.translate, + }; case ActionTypes.UpdateOrderSalt: { return { @@ -101,6 +107,13 @@ export function reducer(state: State = INITIAL_STATE, action: Action) { }; } + case ActionTypes.UpdateSelectedLanguage: { + return { + ...state, + translate: new Translate(action.data), + }; + } + case ActionTypes.UpdateNodeVersion: { return { ...state, diff --git a/packages/website/ts/types.ts b/packages/website/ts/types.ts index 645c9cc11..f6413eec5 100644 --- a/packages/website/ts/types.ts +++ b/packages/website/ts/types.ts @@ -140,6 +140,7 @@ export enum ActionTypes { HideFlashMessage = 'HIDE_FLASH_MESSAGE', UpdateProviderType = 'UPDATE_PROVIDER_TYPE', UpdateInjectedProviderName = 'UPDATE_INJECTED_PROVIDER_NAME', + UpdateSelectedLanguage = 'UPDATE_SELECTED_LANGUAGE', } export interface Action { @@ -667,4 +668,79 @@ export interface MaterialUIPosition { horizontal: 'left' | 'middle' | 'right'; } +export enum Language { + English = 'EN', + Spanish = 'ES', + Chinese = 'ZH', + Korean = 'KO', + Russian = 'RU', +} + +export enum Key { + TopHeader = 'TOP_HEADER', + TopTagline = 'TOP_TAGLINE', + BuildCallToAction = 'BUILD_CALL_TO_ACTION', + CommunityCallToAction = 'COMMUNITY_CALL_TO_ACTION', + ProjectsHeader = 'PROJECTS_HEADER', + FullListPrompt = 'FULL_LIST_PROMPT', + FullListLink = 'FULL_LIST_LINK', + TokenizedSectionHeader = 'TOKENIZED_SECTION_HEADER', + TokenizedSectionDescription = 'TOKENIZED_SECTION_DESCRIPTION', + Currency = 'CURRENCY', + TraditionalAssets = 'TRADITIONAL_ASSETS', + DigitalGoods = 'DIGITAL_GOODS', + OffChainOrderRelay = 'OFFCHAIN_ORDER_RELAY', + OonChainSettlement = 'OONCHAIN_SETTLEMENT', + OffChainOnChainDescription = 'OFFCHAIN_ONCHAIN_DESCRIPTION', + RelayersHeader = 'RELAYERS_HEADER', + BenefitsHeader = 'BENEFITS_HEADER', + BenefitOneTitle = 'BENEFIT_ONE_TITLE', + BenefitOneDescription = 'BENEFIT_ONE_DESCRIPTION', + BenefitTwoTitle = 'BENEFIT_TWO_TITLE', + BenefitTwoDescription = 'BENEFIT_TWO_DESCRIPTION', + BenefitThreeTitle = 'BENEFIT_THREE_TITLE', + BenefitThreeDescription = 'BENEFIT_THREE_DESCRIPTION', + BuildingBlockSectionHeader = 'BUILDING_BLOCK_SECTION_HEADER', + BuildingBlockSectionDescription = 'BUILDING_BLOCK_SECTION_DESCRIPTION', + DevToolsPrompt = 'DEV_TOOLS_PROMPT', + SmartContract = 'SMART_CONTRACT', + Docs = 'DOCS', + DecentralizedGovernance = 'DECENTRALIZED_GOVERNANCE', + DecentralizedGovernanceDescription = 'DECENTRALIZED_GOVERNANCE_DESCRIPTION', + PredictionMarkets = 'PREDICTION_MARKETS', + PredictionMarketsDescription = 'PREDICTION_MARKETS_DESCRIPTION', + StableTokens = 'STABLE_TOKENS', + StableTokensDescription = 'STABLE_TOKENS_DESCRIPTION', + DecentralizedLoans = 'DECENTRALIZED_LOANS', + DecentralizedLoansDescription = 'DECENTRALIZED_LOANS_DESCRIPTION', + FundManagement = 'FUND_MANAGEMENT', + FundManagementDescription = 'FUND_MANAGEMENT_DESCRIPTION', + FinalCallToAction = 'FINAL_CALL_TO_ACTION', + Documentation = 'DOCUMENTATION', + Community = 'COMMUNITY', + Organization = 'ORGANIZATION', + About = 'ABOUT', + Careers = 'CAREERS', + Contact = 'CONTACT', + Blog = 'BLOG', + Forum = 'FORUM', + Connect = 'CONNECT', + Whitepaper = 'WHITEPAPER', + Wiki = 'WIKI', + And = 'AND', + Faq = 'FAQ', + SmartContracts = 'SMART_CONTRACTS', + StandardRelayerApi = 'STANDARD_RELAYER_API', + PortalDApp = 'PORTAL_DAPP', + Website = 'WEBSITE', + Developers = 'DEVELOPERS', + Home = 'HOME', + RocketChat = 'ROCKETCHAT', +} + +export enum Deco { + Cap, + CapWords, + Upper, +} // tslint:disable:max-file-line-count diff --git a/packages/website/ts/utils/translate.ts b/packages/website/ts/utils/translate.ts new file mode 100644 index 000000000..5148e48ad --- /dev/null +++ b/packages/website/ts/utils/translate.ts @@ -0,0 +1,83 @@ +import * as _ from 'lodash'; +import { Deco, Key, Language } from 'ts/types'; + +import * as chinese from '../../translations/chinese.json'; +import * as english from '../../translations/english.json'; +import * as korean from '../../translations/korean.json'; +import * as russian from '../../translations/russian.json'; +import * as spanish from '../../translations/spanish.json'; + +const languageToTranslations = { + [Language.English]: english, + [Language.Spanish]: spanish, + [Language.Chinese]: chinese, + [Language.Korean]: korean, + [Language.Russian]: russian, +}; + +const languagesWithoutCaps = [Language.Chinese, Language.Korean]; + +interface Translation { + [key: string]: string; +} + +export class Translate { + private _selectedLanguage: Language; + private _translation: Translation; + constructor(desiredLanguage?: Language) { + if (!_.isUndefined(desiredLanguage)) { + this.setLanguage(desiredLanguage); + return; + } + const browserLanguage = (window.navigator as any).userLanguage || window.navigator.language || 'en-US'; + let language = Language.English; + if (_.includes(browserLanguage, 'es-')) { + language = Language.Spanish; + } else if (_.includes(browserLanguage, 'zh-')) { + language = Language.Chinese; + } else if (_.includes(browserLanguage, 'ko-')) { + language = Language.Korean; + } else if (_.includes(browserLanguage, 'ru-')) { + language = Language.Russian; + } + this.setLanguage(language); + } + public getLanguage() { + return this._selectedLanguage; + } + public setLanguage(language: Language) { + const isLanguageSupported = !_.isUndefined(languageToTranslations[language]); + if (!isLanguageSupported) { + throw new Error(`${language} not supported`); + } + this._selectedLanguage = language; + this._translation = languageToTranslations[language]; + } + public get(key: Key, decoration?: Deco) { + let text = this._translation[key]; + if (!_.isUndefined(decoration) && !_.includes(languagesWithoutCaps, this._selectedLanguage)) { + switch (decoration) { + case Deco.Cap: + text = this._capitalize(text); + break; + + case Deco.Upper: + text = text.toUpperCase(); + break; + + case Deco.CapWords: + const words = text.split(' '); + const capitalizedWords = _.map(words, w => this._capitalize(w)); + text = capitalizedWords.join(' '); + break; + + default: + throw new Error(`Unrecognized decoration: ${decoration}`); + } + } + return text; + } + private _capitalize(text: string) { + return `${text.charAt(0).toUpperCase()}${text.slice(1)}`; + } +} |