From 9d04de1c8d3efb745cfcae3519cee016751b86ec Mon Sep 17 00:00:00 2001 From: Nolan Lawson Date: Mon, 22 May 2017 06:06:06 -0700 Subject: [PATCH] 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 --- .../mastodon/containers/mastodon.js | 61 ++--------------- app/javascript/mastodon/locales/index.js | 66 ++----------------- app/views/layouts/application.html.haml | 1 + config/webpack/generateLocalePacks.js | 52 +++++++++++++++ config/webpack/shared.js | 19 +++++- package.json | 2 + yarn.lock | 28 ++------ 7 files changed, 90 insertions(+), 139 deletions(-) create mode 100644 config/webpack/generateLocalePacks.js diff --git a/app/javascript/mastodon/containers/mastodon.js b/app/javascript/mastodon/containers/mastodon.js index ac44063a26..1d481d890e 100644 --- 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 ( - + diff --git a/app/javascript/mastodon/locales/index.js b/app/javascript/mastodon/locales/index.js index 2c592026e2..421cb7fab0 100644 --- 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 index 21590ae3cc..f991bc74f5 100755 --- 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 new file mode 100644 index 0000000000..10a66e994d --- /dev/null +++ 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 index 4986ea24d3..1d75e2af21 100644 --- 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 index 23bbf0338e..b7a272ea59 100644 --- 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 index 04960d083a..c2573bb474 100644 --- 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"