about summary refs log tree commit diff
diff options
context:
space:
mode:
authorNolan Lawson <nolan@nolanlawson.com>2017-05-22 06:06:06 -0700
committerEugen Rochko <eugen@zeonfederated.com>2017-05-22 15:06:06 +0200
commit9d04de1c8d3efb745cfcae3519cee016751b86ec (patch)
tree8242677e98f04c66acf6c0531d0741a238b3c360
parent73e4468ff31337d1a0afdc70e1717cb5cfae2e82 (diff)
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
-rw-r--r--app/javascript/mastodon/containers/mastodon.js61
-rw-r--r--app/javascript/mastodon/locales/index.js66
-rwxr-xr-xapp/views/layouts/application.html.haml1
-rw-r--r--config/webpack/generateLocalePacks.js52
-rw-r--r--config/webpack/shared.js19
-rw-r--r--package.json2
-rw-r--r--yarn.lock28
7 files changed, 90 insertions, 139 deletions
diff --git a/app/javascript/mastodon/containers/mastodon.js b/app/javascript/mastodon/containers/mastodon.js
index ac44063a2..1d481d890 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 (
-      <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
index 2c592026e..421cb7fab 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 21590ae3c..f991bc74f 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 000000000..10a66e994
--- /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 4986ea24d..1d75e2af2 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 23bbf0338..b7a272ea5 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 04960d083..c2573bb47 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"