commit: 9d04de1c8d3efb745cfcae3519cee016751b86ec
parent: 73e4468ff31337d1a0afdc70e1717cb5cfae2e82
Author: Nolan Lawson <nolan@nolanlawson.com>
Date: Mon, 22 May 2017 06:06:06 -0700
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
Diffstat:
7 files changed, 90 insertions(+), 139 deletions(-)
diff --git a/app/javascript/mastodon/containers/mastodon.js b/app/javascript/mastodon/containers/mastodon.js
@@ -41,34 +41,12 @@ import FavouritedStatuses from '../features/favourited_statuses';
import Blocks from '../features/blocks';
import Mutes from '../features/mutes';
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 createStream from '../stream';
+import { IntlProvider, addLocaleData } from 'react-intl';
+import { getLocale } from '../locales';
+const { localeData, messages } = getLocale();
+addLocaleData(localeData);
const store = configureStore();
const initialState = JSON.parse(document.getElementById("initial-state").textContent);
@@ -78,33 +56,6 @@ const browserHistory = useRouterHistory(createBrowserHistory)({
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 {
componentDidMount() {
@@ -145,7 +96,7 @@ class Mastodon extends React.PureComponent {
store.dispatch(deleteFromTimelines(data.payload));
break;
case 'notification':
- store.dispatch(updateNotifications(JSON.parse(data.payload), getMessagesForLocale(locale), locale));
+ store.dispatch(updateNotifications(JSON.parse(data.payload), messages, locale));
break;
}
},
@@ -183,7 +134,7 @@ class Mastodon extends React.PureComponent {
const { locale } = this.props;
return (
- <IntlProvider locale={locale} messages={getMessagesForLocale(locale)}>
+ <IntlProvider locale={locale} messages={messages}>
<Provider store={store}>
<Router history={browserHistory} render={applyRouterMiddleware(useScroll())}>
<Route path='/' component={UI}>
diff --git a/app/javascript/mastodon/locales/index.js b/app/javascript/mastodon/locales/index.js
@@ -1,61 +1,9 @@
-import ar from './ar.json';
-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';
+let theLocale;
-const locales = {
- ar,
- 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 function setLocale(locale) {
+ theLocale = locale;
+}
-export default function getMessagesForLocale(locale) {
- return locales[locale];
-};
+export function getLocale() {
+ return theLocale;
+}
diff --git a/app/views/layouts/application.html.haml b/app/views/layouts/application.html.haml
@@ -20,6 +20,7 @@
= stylesheet_pack_tag 'application', media: 'all'
= javascript_pack_tag 'common', integrity: true, crossorigin: 'anonymous'
+ = javascript_pack_tag "locale_#{I18n.locale}", integrity: true, crossorigin: 'anonymous'
= csrf_meta_tags
= yield :header_tags
diff --git a/config/webpack/generateLocalePacks.js b/config/webpack/generateLocalePacks.js
@@ -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;
+
+
diff --git a/config/webpack/shared.js b/config/webpack/shared.js
@@ -10,15 +10,20 @@ const ExtractTextPlugin = require('extract-text-webpack-plugin');
const ManifestPlugin = require('webpack-manifest-plugin');
const extname = require('path-complete-extname');
const { env, paths, publicPath, loadersDir } = require('./configuration.js');
+const localePackPaths = require('./generateLocalePacks');
const extensionGlob = `**/*{${paths.extensions.join(',')}}*`;
const packPaths = sync(join(paths.source, paths.entry, extensionGlob));
+const entryPacks = [].concat(packPaths).concat(localePackPaths);
module.exports = {
- entry: packPaths.reduce(
+ entry: entryPacks.reduce(
(map, entry) => {
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);
return localMap;
}, {}
@@ -41,7 +46,15 @@ module.exports = {
new ManifestPlugin({ fileName: paths.manifest, publicPath, writeToFileEmit: true }),
new webpack.optimize.CommonsChunkPlugin({
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;
+ },
}),
],
diff --git a/package.json b/package.json
@@ -58,6 +58,7 @@
"is-nan": "^1.2.1",
"js-yaml": "^3.8.3",
"lodash": "^4.17.4",
+ "mkdirp": "^0.5.1",
"node-sass": "^4.5.2",
"npmlog": "^4.0.2",
"object-assign": "^4.1.1",
@@ -91,6 +92,7 @@
"redux-immutable": "^3.1.0",
"redux-thunk": "^2.2.0",
"reselect": "^2.5.4",
+ "rimraf": "^2.6.1",
"sass-loader": "^6.0.3",
"stringz": "^0.1.2",
"style-loader": "^0.16.1",
diff --git a/yarn.lock b/yarn.lock
@@ -5407,15 +5407,6 @@ react-redux-loading-bar@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"
-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:
version "5.0.4"
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"
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:
version "2.1.1"
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:
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:
version "0.2.0"
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"
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:
version "2.2.0"
resolved "https://registry.yarnpkg.com/seed-random/-/seed-random-2.2.0.tgz#2a9b19e250a817099231a5b99a4daf80b7fbed54"