Only load Intl data for current language (#3130)

* Only load Intl data for current language

* Extract common chunk only from application.js and public.js

* Generate locale packs, avoid caching on window object
This commit is contained in:
Nolan Lawson 2017-05-22 06:06:06 -07:00 committed by Eugen Rochko
parent 73e4468ff3
commit 9d04de1c8d
7 changed files with 90 additions and 139 deletions

View File

@ -41,34 +41,12 @@ import FavouritedStatuses from '../features/favourited_statuses';
import Blocks from '../features/blocks'; import Blocks from '../features/blocks';
import Mutes from '../features/mutes'; import Mutes from '../features/mutes';
import Report from '../features/report'; import Report from '../features/report';
import { IntlProvider, addLocaleData } from 'react-intl';
import ar from 'react-intl/locale-data/ar';
import bg from 'react-intl/locale-data/bg';
import ca from 'react-intl/locale-data/ca';
import de from 'react-intl/locale-data/de';
import en from 'react-intl/locale-data/en';
import eo from 'react-intl/locale-data/eo';
import es from 'react-intl/locale-data/es';
import fa from 'react-intl/locale-data/fa';
import fi from 'react-intl/locale-data/fi';
import fr from 'react-intl/locale-data/fr';
import he from 'react-intl/locale-data/he';
import hr from 'react-intl/locale-data/hr';
import hu from 'react-intl/locale-data/hu';
import id from 'react-intl/locale-data/id';
import it from 'react-intl/locale-data/it';
import ja from 'react-intl/locale-data/ja';
import nl from 'react-intl/locale-data/nl';
import no from 'react-intl/locale-data/no';
import oc from '../locales/locale-data/oc';
import pt from 'react-intl/locale-data/pt';
import ru from 'react-intl/locale-data/ru';
import uk from 'react-intl/locale-data/uk';
import zh from 'react-intl/locale-data/zh';
import tr from 'react-intl/locale-data/tr';
import getMessagesForLocale from '../locales';
import { hydrateStore } from '../actions/store'; import { hydrateStore } from '../actions/store';
import createStream from '../stream'; import createStream from '../stream';
import { IntlProvider, addLocaleData } from 'react-intl';
import { getLocale } from '../locales';
const { localeData, messages } = getLocale();
addLocaleData(localeData);
const store = configureStore(); const store = configureStore();
const initialState = JSON.parse(document.getElementById("initial-state").textContent); const initialState = JSON.parse(document.getElementById("initial-state").textContent);
@ -78,33 +56,6 @@ const browserHistory = useRouterHistory(createBrowserHistory)({
basename: '/web', basename: '/web',
}); });
addLocaleData([
...ar,
...bg,
...ca,
...de,
...en,
...eo,
...es,
...fa,
...fi,
...fr,
...he,
...hr,
...hu,
...id,
...it,
...ja,
...nl,
...no,
...oc,
...pt,
...ru,
...uk,
...zh,
...tr,
]);
class Mastodon extends React.PureComponent { class Mastodon extends React.PureComponent {
componentDidMount() { componentDidMount() {
@ -145,7 +96,7 @@ class Mastodon extends React.PureComponent {
store.dispatch(deleteFromTimelines(data.payload)); store.dispatch(deleteFromTimelines(data.payload));
break; break;
case 'notification': case 'notification':
store.dispatch(updateNotifications(JSON.parse(data.payload), getMessagesForLocale(locale), locale)); store.dispatch(updateNotifications(JSON.parse(data.payload), messages, locale));
break; break;
} }
}, },
@ -183,7 +134,7 @@ class Mastodon extends React.PureComponent {
const { locale } = this.props; const { locale } = this.props;
return ( return (
<IntlProvider locale={locale} messages={getMessagesForLocale(locale)}> <IntlProvider locale={locale} messages={messages}>
<Provider store={store}> <Provider store={store}>
<Router history={browserHistory} render={applyRouterMiddleware(useScroll())}> <Router history={browserHistory} render={applyRouterMiddleware(useScroll())}>
<Route path='/' component={UI}> <Route path='/' component={UI}>

View File

@ -1,61 +1,9 @@
import ar from './ar.json'; let theLocale;
import en from './en.json';
import ca from './ca.json';
import de from './de.json';
import es from './es.json';
import fa from './fa.json';
import he from './he.json';
import hr from './hr.json';
import hu from './hu.json';
import io from './io.json';
import it from './it.json';
import fr from './fr.json';
import nl from './nl.json';
import no from './no.json';
import oc from './oc.json';
import pt from './pt.json';
import pt_br from './pt-BR.json';
import uk from './uk.json';
import fi from './fi.json';
import eo from './eo.json';
import ru from './ru.json';
import ja from './ja.json';
import zh_hk from './zh-HK.json';
import zh_cn from './zh-CN.json';
import bg from './bg.json';
import id from './id.json';
import tr from './tr.json';
const locales = { export function setLocale(locale) {
ar, theLocale = locale;
en, }
ca,
de,
es,
fa,
he,
hr,
hu,
io,
it,
fr,
nl,
no,
oc,
pt,
'pt-BR': pt_br,
uk,
fi,
eo,
ru,
ja,
'zh-HK': zh_hk,
'zh-CN': zh_cn,
bg,
id,
tr,
};
export default function getMessagesForLocale(locale) { export function getLocale() {
return locales[locale]; return theLocale;
}; }

View File

@ -20,6 +20,7 @@
= stylesheet_pack_tag 'application', media: 'all' = stylesheet_pack_tag 'application', media: 'all'
= javascript_pack_tag 'common', integrity: true, crossorigin: 'anonymous' = javascript_pack_tag 'common', integrity: true, crossorigin: 'anonymous'
= javascript_pack_tag "locale_#{I18n.locale}", integrity: true, crossorigin: 'anonymous'
= csrf_meta_tags = csrf_meta_tags
= yield :header_tags = yield :header_tags

View File

@ -0,0 +1,52 @@
// To avoid adding a lot of boilerplate, locale packs are
// automatically generated here. These are written into the tmp/
// directory and then used to generate locale_en.js, locale_fr.js, etc.
const fs = require('fs');
const path = require('path');
const rimraf = require('rimraf');
const mkdirp = require('mkdirp');
const localesJsonPath = path.join(__dirname, '../../app/javascript/mastodon/locales');
const locales = fs.readdirSync(localesJsonPath).filter(filename => {
return /\.json$/.test(filename) &&
!/defaultMessages/.test(filename) &&
!/whitelist/.test(filename);
}).map(filename => filename.replace(/\.json$/, ''));
const outPath = path.join(__dirname, '../../tmp/packs');
rimraf.sync(outPath);
mkdirp.sync(outPath);
const outPaths = [];
locales.forEach(locale => {
const localePath = path.join(outPath, `locale_${locale}.js`);
const baseLocale = locale.split('-')[0]; // e.g. 'zh-TW' -> 'zh'
const localeDataPath = [
// first try react-intl
`../../node_modules/react-intl/locale-data/${baseLocale}.js`,
// then check locales/locale-data
`../../app/javascript/mastodon/locales/locale-data/${baseLocale}.js`,
// fall back to English (this is what react-intl does anyway)
`../../node_modules/react-intl/locale-data/en.js`,
].filter(filename => fs.existsSync(path.join(outPath, filename)))
.map(filename => filename.replace(/..\/..\/node_modules\//, ''))[0];
const localeContent = `//
// locale_${locale}.js
// automatically generated by generateLocalePacks.js
//
import messages from '../../app/javascript/mastodon/locales/${locale}.json';
import localeData from ${JSON.stringify(localeDataPath)};
import { setLocale } from '../../app/javascript/mastodon/locales';
setLocale({messages, localeData});
`;
fs.writeFileSync(localePath, localeContent, 'utf8');
outPaths.push(localePath);
});
module.exports = outPaths;

View File

@ -10,15 +10,20 @@ const ExtractTextPlugin = require('extract-text-webpack-plugin');
const ManifestPlugin = require('webpack-manifest-plugin'); const ManifestPlugin = require('webpack-manifest-plugin');
const extname = require('path-complete-extname'); const extname = require('path-complete-extname');
const { env, paths, publicPath, loadersDir } = require('./configuration.js'); const { env, paths, publicPath, loadersDir } = require('./configuration.js');
const localePackPaths = require('./generateLocalePacks');
const extensionGlob = `**/*{${paths.extensions.join(',')}}*`; const extensionGlob = `**/*{${paths.extensions.join(',')}}*`;
const packPaths = sync(join(paths.source, paths.entry, extensionGlob)); const packPaths = sync(join(paths.source, paths.entry, extensionGlob));
const entryPacks = [].concat(packPaths).concat(localePackPaths);
module.exports = { module.exports = {
entry: packPaths.reduce( entry: entryPacks.reduce(
(map, entry) => { (map, entry) => {
const localMap = map; const localMap = map;
const namespace = relative(join(paths.source, paths.entry), dirname(entry)); let namespace = relative(join(paths.source, paths.entry), dirname(entry));
if (namespace === '../../../tmp/packs') {
namespace = ''; // generated by generateLocalePacks.js
}
localMap[join(namespace, basename(entry, extname(entry)))] = resolve(entry); localMap[join(namespace, basename(entry, extname(entry)))] = resolve(entry);
return localMap; return localMap;
}, {} }, {}
@ -41,7 +46,15 @@ module.exports = {
new ManifestPlugin({ fileName: paths.manifest, publicPath, writeToFileEmit: true }), new ManifestPlugin({ fileName: paths.manifest, publicPath, writeToFileEmit: true }),
new webpack.optimize.CommonsChunkPlugin({ new webpack.optimize.CommonsChunkPlugin({
name: 'common', name: 'common',
minChunks: 2, minChunks: (module, count) => {
if (module.resource && /node_modules\/react-intl/.test(module.resource)) {
// skip react-intl because it's useless to put in the common chunk,
// e.g. because "shared" modules between zh-TW and zh-CN will never
// be loaded together
return false;
}
return count >= 2;
},
}), }),
], ],

View File

@ -58,6 +58,7 @@
"is-nan": "^1.2.1", "is-nan": "^1.2.1",
"js-yaml": "^3.8.3", "js-yaml": "^3.8.3",
"lodash": "^4.17.4", "lodash": "^4.17.4",
"mkdirp": "^0.5.1",
"node-sass": "^4.5.2", "node-sass": "^4.5.2",
"npmlog": "^4.0.2", "npmlog": "^4.0.2",
"object-assign": "^4.1.1", "object-assign": "^4.1.1",
@ -91,6 +92,7 @@
"redux-immutable": "^3.1.0", "redux-immutable": "^3.1.0",
"redux-thunk": "^2.2.0", "redux-thunk": "^2.2.0",
"reselect": "^2.5.4", "reselect": "^2.5.4",
"rimraf": "^2.6.1",
"sass-loader": "^6.0.3", "sass-loader": "^6.0.3",
"stringz": "^0.1.2", "stringz": "^0.1.2",
"style-loader": "^0.16.1", "style-loader": "^0.16.1",

View File

@ -5407,15 +5407,6 @@ react-redux-loading-bar@2.4.1:
version "2.4.1" version "2.4.1"
resolved "https://registry.yarnpkg.com/react-redux-loading-bar/-/react-redux-loading-bar-2.4.1.tgz#8df64db362f065b5453fbbb7379a5cf62440129a" resolved "https://registry.yarnpkg.com/react-redux-loading-bar/-/react-redux-loading-bar-2.4.1.tgz#8df64db362f065b5453fbbb7379a5cf62440129a"
react-redux@^4.4.5:
version "4.4.5"
resolved "https://registry.yarnpkg.com/react-redux/-/react-redux-4.4.5.tgz#f509a2981be2252d10c629ef7c559347a4aec457"
dependencies:
hoist-non-react-statics "^1.0.3"
invariant "^2.0.0"
lodash "^4.2.0"
loose-envify "^1.1.0"
react-redux@^5.0.4: react-redux@^5.0.4:
version "5.0.4" version "5.0.4"
resolved "https://registry.yarnpkg.com/react-redux/-/react-redux-5.0.4.tgz#1563babadcfb2672f57f9ceaa439fb16bf85d55b" resolved "https://registry.yarnpkg.com/react-redux/-/react-redux-5.0.4.tgz#1563babadcfb2672f57f9ceaa439fb16bf85d55b"
@ -5476,12 +5467,6 @@ react-test-renderer@^15.5.4:
fbjs "^0.8.9" fbjs "^0.8.9"
object-assign "^4.1.0" object-assign "^4.1.0"
react-themeable@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/react-themeable/-/react-themeable-1.1.0.tgz#7d4466dd9b2b5fa75058727825e9f152ba379a0e"
dependencies:
object-assign "^3.0.0"
react-toggle@^2.1.1: react-toggle@^2.1.1:
version "2.1.1" version "2.1.1"
resolved "https://registry.yarnpkg.com/react-toggle/-/react-toggle-2.1.1.tgz#80600a64417a1acc8aaa4c1477f7fbdb88b988fb" resolved "https://registry.yarnpkg.com/react-toggle/-/react-toggle-2.1.1.tgz#80600a64417a1acc8aaa4c1477f7fbdb88b988fb"
@ -5792,6 +5777,12 @@ rimraf@2, rimraf@^2.2.8, rimraf@~2.5.0, rimraf@~2.5.1:
dependencies: dependencies:
glob "^7.0.5" glob "^7.0.5"
rimraf@^2.6.1:
version "2.6.1"
resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.1.tgz#c2338ec643df7a1b7fe5c54fa86f57428a55f33d"
dependencies:
glob "^7.0.5"
ripemd160@0.2.0: ripemd160@0.2.0:
version "0.2.0" version "0.2.0"
resolved "https://registry.yarnpkg.com/ripemd160/-/ripemd160-0.2.0.tgz#2bf198bde167cacfa51c0a928e84b68bbe171fce" resolved "https://registry.yarnpkg.com/ripemd160/-/ripemd160-0.2.0.tgz#2bf198bde167cacfa51c0a928e84b68bbe171fce"
@ -5843,13 +5834,6 @@ scroll-behavior@^0.8.0:
dom-helpers "^2.4.0" dom-helpers "^2.4.0"
invariant "^2.2.1" invariant "^2.2.1"
scss-tokenizer@^0.2.3:
version "0.2.3"
resolved "https://registry.yarnpkg.com/scss-tokenizer/-/scss-tokenizer-0.2.3.tgz#8eb06db9a9723333824d3f5530641149847ce5d1"
dependencies:
js-base64 "^2.1.8"
source-map "^0.4.2"
seed-random@2.2.0: seed-random@2.2.0:
version "2.2.0" version "2.2.0"
resolved "https://registry.yarnpkg.com/seed-random/-/seed-random-2.2.0.tgz#2a9b19e250a817099231a5b99a4daf80b7fbed54" resolved "https://registry.yarnpkg.com/seed-random/-/seed-random-2.2.0.tgz#2a9b19e250a817099231a5b99a4daf80b7fbed54"