├── .gitignore ├── LICENSE ├── README.md ├── lib ├── i18n │ ├── escape.js │ ├── index.js │ ├── react.js │ └── rollbar.js └── plugin │ ├── index.js │ └── loader.js └── package.json /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | npm-debug.log 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Alex Chrome 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # webpack-rails-i18n-js-plugin 2 | 3 | ## Usage 4 | ```js 5 | // webpack.config.js 6 | var WebpackRailsI18nJS = require('webpack-rails-i18n-js-plugin') 7 | 8 | plugins: [ 9 | new WebpackRailsI18nJS({ 10 | // Locale to be used. Default value: 'html.lang'. Can be ether: 11 | // 1. Locale name ("en" for example) 12 | // 2. "html.lang" locale will be taken from `` atribute 13 | locale: 'html.lang', 14 | 15 | // Deafult local. Default value: 'en' 16 | defaultLocale: 'en', 17 | 18 | // Path to yaml files. Default value: `./config/locales` 19 | localesPath: __dirname 20 | }) 21 | ] 22 | ``` 23 | 24 | 25 | ```js 26 | var I18n = require('i18n'); 27 | 28 | console.log(I18n.translate('hello')) 29 | ``` 30 | 31 | More info about client API you can found at https://github.com/fnando/i18n-js 32 | -------------------------------------------------------------------------------- /lib/i18n/escape.js: -------------------------------------------------------------------------------- 1 | function escapeHtml(string) { 2 | var entityMap = { 3 | '&': '&', 4 | '<': '<', 5 | '>': '>', 6 | '"': '"', 7 | "'": ''', 8 | '/': '/' 9 | } 10 | return string.replace(/[&<>"'\/]/g, function (s) { 11 | return entityMap[s]; 12 | }) 13 | } 14 | 15 | function escapeOptions(options) { 16 | var cleanOptions = {} 17 | for (var k in options) { 18 | cleanOptions[k] = typeof options[k] == 'string' ? escapeHtml(options[k]) : options[k] 19 | } 20 | return cleanOptions 21 | } 22 | 23 | module.exports = escapeOptions 24 | -------------------------------------------------------------------------------- /lib/i18n/index.js: -------------------------------------------------------------------------------- 1 | var I18nJS = require('i18n-js') 2 | var createReactElements = require('./react') 3 | var createRollbarReporter = require('./rollbar') 4 | 5 | I18nJS.reset() 6 | 7 | var options = //OPTIONS// 8 | {} 9 | 10 | var currentLocale = options.locale 11 | 12 | if (currentLocale == 'html.lang') { 13 | if (typeof document !== 'undefined') { 14 | currentLocale = document.querySelector('html').lang 15 | if (!currentLocale) { 16 | currentLocale = options.defaultLocale 17 | } 18 | } else { 19 | currentLocale = options.defaultLocale 20 | } 21 | } 22 | 23 | for(key in options) { 24 | I18nJS[key] = options[key] 25 | } 26 | 27 | I18nJS.locale = currentLocale 28 | I18nJS.translations = //TRANSLATIONS// 29 | {} 30 | 31 | var I18n = {} 32 | 33 | for (var key in I18nJS) { 34 | if (typeof I18nJS[key] == 'function') { 35 | I18n[key] = I18nJS[key].bind(I18nJS) 36 | } 37 | } 38 | 39 | createReactElements(I18n) 40 | createRollbarReporter(I18nJS, options) 41 | 42 | module.exports = I18n 43 | -------------------------------------------------------------------------------- /lib/i18n/react.js: -------------------------------------------------------------------------------- 1 | var escapeOptions = require('./escape') 2 | var omit = require('lodash/object/omit') 3 | 4 | function reactElement(React, scope, content) { 5 | if (scope.match(/_html$/)) { 6 | return React.createElement('span', {dangerouslySetInnerHTML: {__html: content}}) 7 | } else { 8 | return React.createElement('span', null, content) 9 | } 10 | } 11 | 12 | function createReactElements(I18n) { 13 | try { 14 | var React = require('react') 15 | } catch(ex) { 16 | return false 17 | } 18 | 19 | I18n.T = function(props) { 20 | var scope = props.scope 21 | var cleanProps = escapeOptions(omit(props, ['scope'])) 22 | 23 | return reactElement(React, scope, I18n.t(scope, cleanProps)) 24 | } 25 | 26 | I18n.P = function(props) { 27 | var scope = props.scope 28 | var count = props.count 29 | var cleanProps = escapeOptions(omit(props, ['scope', 'count'])) 30 | 31 | return reactElement(React, scope, I18n.p(count, scope, cleanProps)) 32 | } 33 | } 34 | 35 | module.exports = createReactElements 36 | -------------------------------------------------------------------------------- /lib/i18n/rollbar.js: -------------------------------------------------------------------------------- 1 | function missingScopeMessage(I18nJS, scope, options) { 2 | var fullScope = I18nJS.getFullScope(scope, options) 3 | var fullScopeWithLocale = [I18nJS.currentLocale(), fullScope].join(I18nJS.defaultSeparator) 4 | return '[missing "' + fullScopeWithLocale + '" translation]'; 5 | } 6 | 7 | function createRollbarReporter(I18nJS, options) { 8 | if (options.rollbarReports) { 9 | var originalHandler = I18nJS.missingTranslation 10 | I18nJS.missingTranslation = function(scope, options) { 11 | if (window && window.Rollbar) { 12 | window.Rollbar.error(missingScopeMessage(I18nJS, scope, options)) 13 | } 14 | return originalHandler.apply(I18nJS, [scope, options]) 15 | } 16 | } 17 | } 18 | 19 | module.exports = createRollbarReporter 20 | -------------------------------------------------------------------------------- /lib/plugin/index.js: -------------------------------------------------------------------------------- 1 | var querystring = require('querystring') 2 | var path = require('path') 3 | var mergeObjects = require('lodash/object/merge') 4 | 5 | var WebpackRailsI18nJSPlugin = function(options) { 6 | this.defaultOptions = { 7 | moduleName: 'i18n', 8 | locale: 'html.lang', 9 | defaultLocale: 'en', 10 | rollbarReports: false, 11 | localesPath: path.join('.', 'config', 'locales') 12 | } 13 | this.options = mergeObjects(this.defaultOptions, options || {}) 14 | } 15 | 16 | WebpackRailsI18nJSPlugin.prototype.apply = function(compiler) { 17 | var options = this.options 18 | var moduleName = this.options.moduleName 19 | var localesPath = this.options.localesPath 20 | 21 | compiler.plugin('normal-module-factory', function(nmf) { 22 | nmf.plugin('after-resolve', function(data, callback) { 23 | if (data.rawRequest === moduleName) { 24 | data.loaders.push(path.join(__dirname, 'loader.js?' + querystring.stringify(options))) 25 | } 26 | callback(null, data) 27 | }) 28 | }) 29 | 30 | compiler.resolvers.normal.plugin('module', function(request, callback) { 31 | if(request.request === moduleName) { 32 | callback(null, { 33 | path: path.join(__dirname, '..', 'i18n', 'index.js'), 34 | query: request.query, 35 | file: true, 36 | resolved: true 37 | }) 38 | } else { 39 | callback(); 40 | } 41 | }) 42 | } 43 | 44 | module.exports = WebpackRailsI18nJSPlugin 45 | 46 | -------------------------------------------------------------------------------- /lib/plugin/loader.js: -------------------------------------------------------------------------------- 1 | var querystring = require('querystring') 2 | var path = require('path') 3 | var yaml = require('js-yaml') 4 | var fs = require('fs') 5 | var glob = require('glob') 6 | var mergeObjects = require('lodash/object/merge') 7 | var omit = require('lodash/object/omit') 8 | 9 | function readLocales(localesPath) { 10 | var files = glob.sync('**/*.yml', {cwd: localesPath}) 11 | return files.reduce(function(locales, localeFile) { 12 | var locale = yaml.safeLoad(fs.readFileSync(path.join(localesPath, localeFile), 'utf8')) 13 | return mergeObjects(locales, locale) 14 | }, {}) 15 | } 16 | 17 | module.exports = function(src) { 18 | var options = querystring.parse(this.query.substr(1)) 19 | var optionsString = JSON.stringify(omit(options, ['moduleName', 'localesPath'])) 20 | var locales = readLocales(options.localesPath) 21 | var localeString = JSON.stringify(locales) 22 | return src 23 | .replace('//OPTIONS//', optionsString) 24 | .replace('//TRANSLATIONS//', localeString) 25 | } 26 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "webpack-rails-i18n-js-plugin", 3 | "version": "1.3.0", 4 | "description": "", 5 | "main": "lib/plugin/index.js", 6 | "author": "", 7 | "repository": { 8 | "type": "git", 9 | "url": "git@github.com:chrome/webpack-rails-i18n-js-plugin.git" 10 | }, 11 | "homepage": "https://github.com/chrome/webpack-rails-i18n-js-plugin", 12 | "license": "MIT", 13 | "dependencies": { 14 | "glob": "^6.0.1", 15 | "i18n-js": "https://github.com/fnando/i18n-js", 16 | "js-yaml": "^3.4.6", 17 | "lodash": "^3.10.1" 18 | } 19 | } 20 | --------------------------------------------------------------------------------