* Only load Intl data for current language * Extract common chunk only from application.js and public.js * Generate locale packs, avoid caching on window objectmaster
@@ -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}> | |||
@@ -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; | |||
} |
@@ -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 | |||
@@ -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; | |||
@@ -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; | |||
}, | |||
}), | |||
], | |||
@@ -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", | |||
@@ -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" | |||