site: improve loading performance (#4112)

* site: should generate html file for both of en and cn, ref: #3934

* site: remove loading animation

* site: fix header menu highlight

* site: improve detail

* deps: update
This commit is contained in:
Benjy Cui 2016-12-09 13:02:16 +08:00 committed by Wei Zhu
parent 3382abde65
commit 60da9909d3
16 changed files with 154 additions and 303 deletions

View File

@ -27,11 +27,11 @@ onClick | set the handler to handle `click` event | function | -
`<Button>Hello world!</Button>` will be rendered into `<button>Hello world!</button>`, and all the properties which are not listed above will be transferred to the `<button>` tag.
<style>
[id^="components-button-demo-"] .ant-btn {
[id^=components-button-demo-] .ant-btn {
margin-right: 8px;
margin-bottom: 12px;
}
[id^="components-button-demo-"] .ant-btn-group > .ant-btn {
[id^=components-button-demo-] .ant-btn-group > .ant-btn {
margin-right: 0;
}
</style>

View File

@ -86,11 +86,11 @@
"babel-preset-es2015": "^6.18.0",
"babel-preset-react": "^6.16.0",
"babel-preset-stage-0": "^6.16.0",
"bisheng": "^0.17.0",
"bisheng": "^0.18.0",
"bisheng-plugin-antd": "~0.6.0",
"bisheng-plugin-description": "^0.1.1",
"bisheng-plugin-react": "^0.3.0",
"bisheng-plugin-toc": "^0.3.0",
"bisheng-plugin-toc": "^0.4.0",
"coveralls": "^2.11.15",
"css-split-webpack-plugin": "^0.2.1",
"dekko": "^0.2.0",

View File

@ -59,6 +59,18 @@ module.exports = {
'bisheng-plugin-react?lang=__react',
'bisheng-plugin-antd',
],
filePathMapper(filePath) {
if (filePath === '/index.html') {
return ['/index.html', '/index-cn.html'];
}
if (filePath.endsWith('/index.html')) {
return [filePath, filePath.replace(/\/index\.html$/, '-cn/index.html')];
}
if (filePath !== '/404.html' && filePath !== '/index-cn.html') {
return [filePath, filePath.replace(/\.html$/, '-cn.html')];
}
return filePath;
},
doraConfig: {
verbose: true,
plugins: ['dora-plugin-upload'],

View File

@ -1,3 +1,4 @@
const homeTmpl = './template/Home/index';
const contentTmpl = './template/Content/index';
module.exports = {
@ -27,8 +28,12 @@ module.exports = {
routes: {
path: '/',
component: './template/Layout/index',
indexRoute: { component: './template/Home/index' },
indexRoute: { component: homeTmpl },
childRoutes: [{
path: 'index-cn',
component: homeTmpl,
dataPath: '/',
}, {
path: 'docs/practice/:children',
component: contentTmpl,
}, {
@ -40,6 +45,11 @@ module.exports = {
}, {
path: 'changelog',
component: contentTmpl,
dataPath: 'CHANGELOG',
}, {
path: 'changelog-cn',
component: contentTmpl,
dataPath: 'CHANGELOG',
}, {
path: 'components/:children/',
component: contentTmpl,

View File

@ -84,202 +84,6 @@ div.main-container {
display: none;
}
.ant-site-loading {
width: 100vw;
text-align: center;
position: absolute;
top: 50%;
margin-top: -50px;
transition: opacity .45s;
pointer-events: none;
opacity: 0;
img {
transform-style: preserve-3d;
display: block;
width: 64px;
margin: 0 auto 10px;
animation: loadTween 2s cubic-bezier(0.785, 0.135, 0.15, 0.86) infinite;
}
}
#react-content[hidden] {
display: none;
+ .ant-site-loading {
opacity: 1;
}
}
#loading-text {
font-family: lato,Helvetica Neue,Helvetica,PingFang SC,Hiragino Sans GB,Microsoft YaHei,Arial,sans-serif;
color: #777;
font-size: 16px;
letter-spacing: 2px;
> span {
display: inline-block;
&:first-child {
animation: xLeftMatrixR 2s cubic-bezier(0.645, 0.045, 0.355, 1) infinite alternate;
}
&:last-child {
animation: xRightMatrixR 2s cubic-bezier(0.645, 0.045, 0.355, 1) infinite alternate;
}
&.blank {
width: 0.4em;
}
}
}
.page-wrapper {
background: #ECECEC;
}
.loop-yoyo-load(@index) when (@index >= 0) {
.yoyo-x-@{index} {
animation: ~"Load@{index}" 2s cubic-bezier(0.645, 0.045, 0.355, 1) infinite alternate;
}
.loop-yoyo-load(@index - 1);
}
.loop-yoyo-load(5);
@keyframes loadTween {
0% {
transform: rotateY(0deg);
}
50% {
transform: rotateY(180deg);
}
100% {
transform: rotateX(-180deg);
}
}
@keyframes xLeftMatrixR {
0% {
opacity: 0;
transform: translateX(-50px) rotate(-30deg) scale(1.5, 1.5);
}
20% {
opacity: 0;
transform: translateX(-50px) rotate(-30deg) scale(1.5, 1.5);
}
80% {
opacity: 1;
transform: translateX(0px) rotate(0deg) scale(1, 1);
}
}
@keyframes xRightMatrixR {
0% {
opacity: 0;
transform: translateX(50px) rotate(30deg) scale(1.5, 1.5);
}
20% {
opacity: 0;
transform: translateX(50px) rotate(30deg) scale(1.5, 1.5);
}
80% {
opacity: 1;
transform: translateX(0px) rotate(0deg) scale(1, 1);
}
}
@keyframes Load5 {
0% {
opacity: 0;
transform: translate(-30px, 30px) rotate(-30deg) scale(1.5, 1.5);
}
20% {
opacity: 0;
transform: translate(-30px, 30px) rotate(-30deg) scale(1.5, 1.5);
}
80% {
opacity: 1;
transform: translate(0, 0) rotate(0deg) scale(1, 1);
}
}
@keyframes Load4 {
0% {
opacity: 0;
transform: translate(30px, -30px) rotate(-30deg) scale(1.5, 1.5);
}
20% {
opacity: 0;
transform: translate(30px, -30px) rotate(-30deg) scale(1.5, 1.5);
}
80% {
opacity: 1;
transform: translate(0, 0) rotate(0deg) scale(1, 1);
}
}
@keyframes Load3 {
0% {
opacity: 0;
transform: translate(-30px, -30px) rotate(30deg) scale(1.5, 1.5);
}
20% {
opacity: 0;
transform: translate(-30px, -30px) rotate(30deg) scale(1.5, 1.5);
}
80% {
opacity: 1;
transform: translate(0, 0) rotate(0deg) scale(1, 1);
}
}
@keyframes Load2 {
0% {
opacity: 0;
transform: translate(-30px, 30px) rotate(-30deg) scale(1.5, 1.5);
}
20% {
opacity: 0;
transform: translate(-30px, 30px) rotate(-30deg) scale(1.5, 1.5);
}
80% {
opacity: 1;
transform: translate(0, 0) rotate(0deg) scale(1, 1);
}
}
@keyframes Load1 {
0% {
opacity: 0;
transform: translate(30px, 30px) rotate(30deg) scale(1.5, 1.5);
}
20% {
opacity: 0;
transform: translate(30px, 30px) rotate(30deg) scale(1.5, 1.5);
}
80% {
opacity: 1;
transform: translate(0, 0) rotate(0deg) scale(1, 1);
}
}
@keyframes Load0 {
0% {
opacity: 0;
transform: translate(30px, -30px) rotate(-30deg) scale(1.5, 1.5);
}
20% {
opacity: 0;
transform: translate(30px, -30px) rotate(-30deg) scale(1.5, 1.5);
}
80% {
opacity: 1;
transform: translate(0, 0) rotate(0deg) scale(1, 1);
}
}
@keyframes upDownMove {
0% {
transform: translateY(0);
}
50% {
transform: translateY(-6px);
}
100% {
transform: translateY(0);
}
}

View File

@ -26,30 +26,34 @@
</script>
</head>
<body>
<div id="react-content" hidden>
<div id="react-content">
{{ content | safe }}
</div>
<div class="ant-site-loading" id="ant-site-loading">
<img src='https://t.alipayobjects.com/images/rmsweb/T1B9hfXcdvXXXXXXXX.svg'/>
<div id="loading-text">One Design Language</div>
</div>
<script>
(function () {
var content = document.getElementById('loading-text');
if (content) {
content.innerHTML = content.innerHTML.split('').map(function (letter, i) {
var className;
if (i > 0 && i < content.innerText.length - 1) {
className = 'yoyo-x-' + (i % 6);
function isZhCN(pathname) {
return /-cn\/?$/.test(pathname);
}
if (letter.trim() === '') {
className += ' blank';
function getLocalizedPathname(path, zhCN) {
var pathname = path.startsWith('/') ? path : '/' + path;
if (!zhCN) { // to enUS
return /\/?index-cn/.test(pathname) ? '/' : pathname.replace('-cn', '');
} else if (pathname === '/') {
return '/index-cn';
} else if (pathname.endsWith('/')) {
return pathname.replace(/\/$/, '-cn/');
}
return '<span class="' + className + '">' + letter + '</span>';
}).join('');
return pathname + '-cn';
}
})();
var queryString = location.search;
if (queryString) {
var pathname = location.pathname;
var isZhCNConfig = queryString.indexOf('zh-CN') > -1;
if (isZhCNConfig !== isZhCN(pathname)) {
location.pathname = getLocalizedPathname(pathname, isZhCNConfig)
}
}
</script>
<script>
// Enable Google Analytics
if (!location.port) {
/* eslint-disable */

View File

@ -9,7 +9,21 @@ import config from '../../';
const SubMenu = Menu.SubMenu;
function getActiveMenuItem(props) {
return props.params.children || props.location.pathname.replace(/^\//, '');
const children = props.params.children;
return (children && children.replace('-cn', '')) ||
props.location.pathname.replace(/(^\/|-cn$)/g, '');
}
function getModuleData(props) {
const pathname = props.location.pathname;
const moduleName = /^\/?components/.test(pathname) ?
'components' : pathname.split('/').filter(item => item).slice(0, 2).join('/');
const moduleData = moduleName === 'components' || moduleName === 'docs/react' ||
moduleName === 'changelog' || moduleName === 'changelog-cn' ?
[...props.picked.components, ...props.picked['docs/react'], ...props.picked.changelog] :
props.picked[moduleName];
const excludedSuffix = utils.isZhCN(props.location.pathname) ? 'en-US.md' : 'zh-CN.md';
return moduleData.filter(({ meta }) => !meta.filename.endsWith(excludedSuffix));
}
function fileNameToPath(filename) {
@ -28,26 +42,17 @@ export default class MainContent extends React.Component {
constructor(props) {
super(props);
this.state = { openKeys: [] };
this.state = { openKeys: this.getSideBarOpenKeys(props) || [] };
}
componentDidMount() {
this.componentWillReceiveProps(this.props);
this.componentDidUpdate();
}
componentWillReceiveProps(nextProps) {
const prevModule = this.currentModule;
this.currentModule = location.pathname.split('/')[2] || 'components';
if (this.currentModule === 'react') {
this.currentModule = 'components';
}
if (prevModule !== this.currentModule) {
const moduleData = this.getModuleData(nextProps);
const shouldOpenKeys = Object.keys(utils.getMenuItems(
moduleData, this.context.intl.locale
));
this.setState({ openKeys: shouldOpenKeys });
const openKeys = this.getSideBarOpenKeys(nextProps);
if (openKeys) {
this.setState({ openKeys });
}
}
@ -73,6 +78,21 @@ export default class MainContent extends React.Component {
this.setState({ openKeys });
}
getSideBarOpenKeys(nextProps) {
const pathname = nextProps.location.pathname;
const prevModule = this.currentModule;
this.currentModule = pathname.replace(/^\//).split('/')[1] || 'components';
if (this.currentModule === 'react') {
this.currentModule = 'components';
}
const locale = utils.isZhCN(pathname) ? 'zh-CN' : 'en-US';
if (prevModule !== this.currentModule) {
const moduleData = getModuleData(nextProps);
const shouldOpenKeys = Object.keys(utils.getMenuItems(moduleData, locale));
return shouldOpenKeys;
}
}
generateMenuItem(isTop, item) {
const locale = this.context.intl.locale;
const key = fileNameToPath(item.filename);
@ -84,7 +104,10 @@ export default class MainContent extends React.Component {
const disabled = item.disabled;
const url = item.filename.replace(/(\/index)?((\.zh-CN)|(\.en-US))?\.md$/i, '').toLowerCase();
const child = !item.link ? (
<Link to={{ query: this.props.location.query, pathname: /^components/.test(url) ? `${url}/` : url }} disabled={disabled}>
<Link
to={utils.getLocalizedPathname(/^components/.test(url) ? `${url}/` : url, locale === 'zh-CN')}
disabled={disabled}
>
{text}
</Link>
) : (
@ -118,20 +141,8 @@ export default class MainContent extends React.Component {
return [...topLevel, ...itemGroups];
}
getModuleData(props) {
const pathname = props.location.pathname;
const moduleName = /^\/?components/.test(pathname) ?
'components' : pathname.split('/').filter(item => item).slice(0, 2).join('/');
const moduleData = moduleName === 'components' || moduleName === 'changelog' || moduleName === 'docs/react' ?
[...props.picked.components, ...props.picked['docs/react'], ...props.picked.changelog] :
props.picked[moduleName];
const locale = this.context.intl.locale;
const excludedSuffix = locale === 'zh-CN' ? 'en-US.md' : 'zh-CN.md';
return moduleData.filter(({ meta }) => !meta.filename.endsWith(excludedSuffix));
}
getMenuItems() {
const moduleData = this.getModuleData(this.props);
const moduleData = getModuleData(this.props);
const menuItems = utils.getMenuItems(
moduleData, this.context.intl.locale
);
@ -194,7 +205,7 @@ export default class MainContent extends React.Component {
</Col>
<Col lg={20} md={18} sm={24} xs={24} className="main-container">
{
props.utils.get(props, 'pageData.demo') ?
props.demos ?
<ComponentDoc {...props} doc={localizedPageData} demos={props.demos} /> :
<Article {...props} content={localizedPageData} />
}

View File

@ -3,10 +3,14 @@ import Promise from 'bluebird';
import MainContent from './MainContent';
import * as utils from '../utils';
const locale = utils.isZhCN() ? 'zh-CN' : 'en-US';
export function collect(nextProps, callback) {
const pageData = nextProps.location.pathname.endsWith('changelog') ?
nextProps.data.CHANGELOG : nextProps.pageData;
const pathname = nextProps.location.pathname;
const locale = utils.isZhCN(pathname) ? 'zh-CN' : 'en-US';
const pageDataPath = pathname.replace('-cn', '').split('/');
let pageData = nextProps.pageData;
if (!pageData && locale === 'zh-CN') {
pageData = nextProps.utils.get(nextProps.data, pageDataPath);
}
if (!pageData) {
callback(404, nextProps);
return;
@ -16,10 +20,7 @@ export function collect(nextProps, callback) {
pageData() : (pageData[locale] || pageData.index[locale] || pageData.index)();
const promises = [pageDataPromise];
const pathname = nextProps.location.pathname;
const demos = nextProps.utils.get(
nextProps.data, [...pathname.split('/'), 'demo']
);
const demos = nextProps.utils.get(nextProps.data, [...pageDataPath, 'demo']);
if (demos) {
promises.push(demos());
}

View File

@ -5,6 +5,7 @@ import ScrollElement from 'rc-scroll-anim/lib/ScrollElement';
import GitHubButton from 'react-github-button';
import { Icon } from 'antd';
import QueueAnim from 'rc-queue-anim';
import * as utils from '../utils';
function typeFunc(a) {
if (a.key === 'line') {
@ -16,7 +17,7 @@ function typeFunc(a) {
}
export default function Banner({ location, onEnterChange }) {
const query = location.query;
const isZhCN = utils.isZhCN(location.pathname);
return (
<section className="page banner-wrapper">
<ScrollElement id="banner" onChange={({ mode }) => onEnterChange(mode)}>
@ -25,10 +26,10 @@ export default function Banner({ location, onEnterChange }) {
<p key="content"><FormattedMessage id="app.home.slogan" /></p>
<span className="line" key="line" />
<div key="button1" className="start-button clearfix">
<Link to={{ query, pathname: '/docs/spec/introduce' }}>
<Link to={utils.getLocalizedPathname('/docs/spec/introduce', isZhCN)}>
<FormattedMessage id="app.home.introduce" />
</Link>
<Link to={{ query, pathname: '/docs/react/introduce' }}>
<Link to={utils.getLocalizedPathname('/docs/react/introduce', isZhCN)}>
<FormattedMessage id="app.home.start" />
</Link>
</div>

View File

@ -5,6 +5,7 @@ import TweenOne from 'rc-tween-one';
import ScrollOverPack from 'rc-scroll-anim/lib/ScrollOverPack';
import { Icon, Button } from 'antd';
import QueueAnim from 'rc-queue-anim';
import * as utils from '../utils';
function onScrollEvent(e) {
const clientHeight = document.documentElement.clientHeight;
@ -32,7 +33,7 @@ export default function Page1({ location }) {
<h2 key="h2"><FormattedMessage id="app.home.best-practice" /></h2>
<p key="p" style={{ maxWidth: 310 }}><FormattedMessage id="app.home.experience" /></p>
<div key="button">
<Link to={{ query: location.query, pathname: '/docs/practice/cases' }}>
<Link to={utils.getLocalizedPathname('/docs/practice/cases', utils.isZhCN(location.pathname))}>
<Button type="primary" size="large">
<FormattedMessage id="app.home.learn-more" />
<Icon type="right" />

View File

@ -5,6 +5,7 @@ import TweenOne from 'rc-tween-one';
import ScrollOverPack from 'rc-scroll-anim/lib/ScrollOverPack';
import { Icon, Button } from 'antd';
import QueueAnim from 'rc-queue-anim';
import * as utils from '../utils';
export default function Page2({ location }) {
return (
@ -18,7 +19,7 @@ export default function Page2({ location }) {
<h2 key="h2"><FormattedMessage id="app.home.design-pattern" /></h2>
<p key="p" style={{ maxWidth: 260 }}><FormattedMessage id="app.home.pattern" /></p>
<div key="button">
<Link to={{ query: location.query, pathname: '/docs/pattern/navigation' }}>
<Link to={utils.getLocalizedPathname('/docs/pattern/navigation', utils.isZhCN(location.pathname))}>
<Button type="primary" size="large">
<FormattedMessage id="app.home.learn-more" />
<Icon type="right" />

View File

@ -5,6 +5,7 @@ import TweenOne from 'rc-tween-one';
import ScrollOverPack from 'rc-scroll-anim/lib/ScrollOverPack';
import { Icon, Button } from 'antd';
import QueueAnim from 'rc-queue-anim';
import * as utils from '../utils';
export default function Page3({ location }) {
return (
@ -21,7 +22,7 @@ export default function Page3({ location }) {
<h2 key="h2"><FormattedMessage id="app.home.reusable-components" /></h2>
<p key="p" style={{ maxWidth: 280 }}><FormattedMessage id="app.home.components-intro" /></p>
<div key="button">
<Link to={{ query: location.query, pathname: '/docs/react/introduce' }}>
<Link to={utils.getLocalizedPathname('/docs/react/introduce', utils.isZhCN(location.pathname))}>
<Button type="primary" size="large">
<FormattedMessage id="app.home.learn-more" />
<Icon type="right" />

View File

@ -3,6 +3,7 @@ import { Link } from 'react-router';
import { FormattedMessage } from 'react-intl';
import classNames from 'classnames';
import { Select, Menu, Row, Col, Icon, Button, Popover } from 'antd';
import * as utils from '../utils';
const Option = Select.Option;
@ -31,7 +32,8 @@ export default class Header extends React.Component {
}
handleSearch = (value) => {
this.context.router.push({ pathname: `${value}/` });
const { intl, router } = this.context;
router.push({ pathname: utils.getLocalizedPathname(`${value}/`, intl.locale === 'zh-CN') });
}
handleSelectFilter = (value, option) => {
@ -39,11 +41,8 @@ export default class Header extends React.Component {
}
handleLangChange = () => {
if (typeof localStorage !== 'undefined') {
const locale = this.context.intl.locale === 'zh-CN' ? 'en-US' : 'zh-CN';
localStorage.setItem('locale', locale);
location.search = `?locale=${locale}`;
}
const pathname = this.props.location.pathname;
location.pathname = utils.getLocalizedPathname(pathname, !utils.isZhCN(pathname));
}
render() {
@ -56,7 +55,8 @@ export default class Header extends React.Component {
}
const locale = this.context.intl.locale;
const excludedSuffix = locale === 'zh-CN' ? 'en-US.md' : 'zh-CN.md';
const isZhCN = locale === 'zh-CN';
const excludedSuffix = isZhCN ? 'en-US.md' : 'zh-CN.md';
const options = components
.filter(({ meta }) => !meta.filename.endsWith(excludedSuffix))
.map(({ meta }) => {
@ -77,39 +77,38 @@ export default class Header extends React.Component {
});
const menuMode = this.state.menuMode;
const query = location.query;
const menu = [
<Button className="lang" type="ghost" size="small" onClick={this.handleLangChange} key="lang">
<FormattedMessage id="app.header.lang" />
</Button>,
<Menu mode={menuMode} selectedKeys={[activeMenuItem]} id="nav" key="nav">
<Menu.Item key="home">
<Link to={{ query, pathname: '/' }}>
<Link to={utils.getLocalizedPathname('/', isZhCN)}>
<FormattedMessage id="app.header.menu.home" />
</Link>
</Menu.Item>
<Menu.Item key="docs/spec">
<Link to={{ query, pathname: '/docs/spec/introduce' }}>
<Link to={utils.getLocalizedPathname('/docs/spec/introduce', isZhCN)}>
<FormattedMessage id="app.header.menu.spec" />
</Link>
</Menu.Item>
<Menu.Item key="docs/react">
<Link to={{ query, pathname: '/docs/react/introduce' }}>
<Link to={utils.getLocalizedPathname('/docs/react/introduce', isZhCN)}>
<FormattedMessage id="app.header.menu.components" />
</Link>
</Menu.Item>
<Menu.Item key="docs/pattern">
<Link to={{ query, pathname: '/docs/pattern/navigation' }}>
<Link to={utils.getLocalizedPathname('/docs/pattern/navigation', isZhCN)}>
<FormattedMessage id="app.header.menu.pattern" />
</Link>
</Menu.Item>
<Menu.Item key="docs/practice">
<Link to={{ query, pathname: '/docs/practice/cases' }}>
<Link to={utils.getLocalizedPathname('/docs/practice/cases', isZhCN)}>
<FormattedMessage id="app.header.menu.practice" />
</Link>
</Menu.Item>
<Menu.Item key="docs/resource">
<Link to={{ query, pathname: '/docs/resource/download' }}>
<Link to={utils.getLocalizedPathname('/docs/resource/download', isZhCN)}>
<FormattedMessage id="app.header.menu.resource" />
</Link>
</Menu.Item>
@ -133,7 +132,7 @@ export default class Header extends React.Component {
</Popover> : null}
<Row>
<Col lg={4} md={6} sm={24} xs={24}>
<Link to={{ query, pathname: '/' }} id="logo">
<Link to={utils.getLocalizedPathname('/', isZhCN)} id="logo">
<img alt="logo" src="https://t.alipayobjects.com/images/rmsweb/T1B9hfXcdvXXXXXXXX.svg" />
<span>Ant Design</span>
</Link>

View File

@ -20,17 +20,21 @@ if (typeof window !== 'undefined') {
/* eslint-enable global-require */
}
const appLocale = utils.isZhCN() ? cnLocale : enLocale;
addLocaleData(appLocale.data);
export default class Layout extends React.Component {
static contextTypes = {
router: React.PropTypes.object.isRequired,
}
state = {
constructor(props) {
super(props);
const pathname = props.location.pathname;
const appLocale = utils.isZhCN(pathname) ? cnLocale : enLocale;
addLocaleData(appLocale.data);
this.state = {
isFirstScreen: true,
appLocale,
};
}
componentDidMount() {
if (typeof ga !== 'undefined') {
@ -60,11 +64,12 @@ export default class Layout extends React.Component {
render() {
const { children, ...restProps } = this.props;
const { appLocale, isFirstScreen } = this.state;
return (
<IntlProvider locale={appLocale.locale} messages={appLocale.messages}>
<LocaleProvider locale={enUS}>
<div className="page-wrapper">
<Header {...restProps} isFirstScreen={this.state.isFirstScreen} />
<Header {...restProps} isFirstScreen={isFirstScreen} />
{cloneElement(children, { onEnterChange: this.onEnterChange })}
<Footer />
</div>

View File

@ -1,12 +1,16 @@
import React from 'react';
import { Link } from 'react-router';
import * as utils from './utils';
export default function NotFound({ location }) {
return (
<div id="page-404">
<section>
<h1>404</h1>
<p>你要找的页面不存在 <Link to={{ query: location.query, pathname: '/' }}>返回首页</Link></p>
<p>
你要找的页面不存在
<Link to={utils.getLocalizedPathname('/', utils.isZhCN(location.pathname))}>返回首页</Link>
</p>
</section>
<style
dangerouslySetInnerHTML={{

View File

@ -19,23 +19,20 @@ export function getMenuItems(moduleData, locale) {
return menuItems;
}
export function isZhCN() {
if (typeof location === 'undefined') {
// Use English in SSR.
return false;
}
if (location.search.indexOf('locale=zh-CN') > -1) {
return true;
}
if (location.search.indexOf('locale=en-US') > -1) {
return false;
export function isZhCN(pathname) {
return /-cn\/?$/.test(pathname);
}
const language = (
typeof localStorage === 'undefined' ||
!localStorage.getItem('locale')
) ? navigator.language : localStorage.getItem('locale');
return language === 'zh-CN';
export function getLocalizedPathname(path, zhCN) {
const pathname = path.startsWith('/') ? path : `/${path}`;
if (!zhCN) { // to enUS
return /\/?index-cn/.test(pathname) ? '/' : pathname.replace('-cn', '');
} else if (pathname === '/') {
return '/index-cn';
} else if (pathname.endsWith('/')) {
return pathname.replace(/\/$/, '-cn/');
}
return `${pathname}-cn`;
}
export function ping(url, callback) {