diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..60c8b25 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,13 @@ +# editorconfig.org + +root = true + +[*] +indent_style = space +indent_size = 2 +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true + +[*.md] +trim_trailing_whitespace = false diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c2658d7 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +node_modules/ diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..2467636 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2019 Infermedica + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..9c94231 --- /dev/null +++ b/README.md @@ -0,0 +1,144 @@ +# Infermedica Object Override Loader + +This Webpack loader aims to override parts of YAML or JSON files with values from corresponding files in a specified +directory. + +## Installation + +```bash +$ npm install --save-dev @infermedica/object-override-loader +``` + +## Usage + +To use this loader, include it in your Webpack configuration file and provide a `objectsDir` path, e.g.: + +```js +{ + test: /\.ya?ml$/, + use: [ + 'js-yaml-loader', + { + loader: '@infermedica/object-override-loader', + options: { + objectsDir: 'path/to/directory' + } + } + ] +}, +``` + +## Package purpose + +This loader has been created specifically to allow overriding of translations in vue-i18n `` blocks. If you have +a core library that is later included in other applications, you can use this loader to override only selected +translations from the core library in other applications. + +Let's say you have following Vue file with `` translation block in your core library: +```yaml + + en: + SegmentFeedback: + question: "Is the information on this site helpful?" + commentLabel: "Comment" + commentLabelOptionl: "(optional)" + sendButtonText: "Send feedback" + +``` + +Other translations can also be loaded globally: +```javascript +import en from './en.yaml'; +import pl from './pl.yaml'; + +Vue.use(VueI18n); + +const i18n = new VueI18n({ + messages: { + en, + pl + } +}); + +new Vue({ + i18n, + render: (h) => h(App) +}).$mount('#app'); +``` + +```yaml +# pl.yaml +SegmentFeedback: + question: "Czy infomacje na tej stornie są pomocne?" + commentLabel: "Komentarz" + commentLabelOptionl: "(opcjonalnie)" + sendButtonText: "Wyślij opinię" +``` + +To override some of the above translation in other project, include this loader in your Webpack configuration file: + +```js +{ + resourceQuery: /blockType=i18n/, + type: 'javascript/auto', + use: [ + '@kazupon/vue-i18n-loader', + 'js-yaml-loader', + { + loader: '@infermedica/object-override-loader', + options: { + objectsDir: 'path/to/directory' + } + } + ] +}, +{ + test: /\.ya?ml$/, + use: [ + 'js-yaml-loader', + { + loader: '@infermedica/object-override-loader', + options: { + objectsDir: 'path/to/directory' + } + } + ] +}, +``` + +The loader looks for overrides in `path/to/directory` directory: + +```yaml +$ ls path/to/directory +en.yaml pl.yaml +``` + +```yaml +# path/to/directory/en.yaml +SegmentFeedback: + question: "How would you rate us?" + sendButtonText: "Send rating" +``` +```yaml +# path/to/directory/pl.yaml +SegmentFeedback: + question: "Jak byś nas ocenił?" + sendButtonText: "Wyślij ocenę" +``` + +After running Webpack, the loader will replace "Is the information on this site helpful?" with +"How would you rate us?" and "Send feedback" with "Send rating" (similarly for Polish translations), +leaving other translations untouched. The final build will contain the merge of core and overridden translations 🎉. + +#### Note + +Although the loader was created to override vue-i18n translations it can be used to update any YAML or JSON file. +Also, even though examples above use YAML files to store translations, it should work the same with JSON objects. + +## Contribution + +Feel free to raise an issue if you have any questions or a similar use case. We're happy to accept pull requests too. + +## License + +MIT Copyright (c) Infermedica diff --git a/index.js b/index.js new file mode 100644 index 0000000..dda3917 --- /dev/null +++ b/index.js @@ -0,0 +1,91 @@ +const fs = require('fs'); +const path = require('path'); +const yaml = require('js-yaml'); +const {getOptions} = require('loader-utils'); + + +const isYAMLFile = (fileExt) => /^\.ya?ml$/.test(fileExt); +const isJSONFile = (fileExt) => /^\.json/.test(fileExt); + + +function updateSourceObject(srcObject, overrideObject) { + for (const srcKey of Object.keys(srcObject)) { + if (overrideObject.hasOwnProperty(srcKey)) { + if (typeof overrideObject[srcKey] === 'object') { + updateSourceObject(srcObject[srcKey], overrideObject[srcKey]); + } else { + srcObject[srcKey] = overrideObject[srcKey]; + } + } + } +} + + +function parseContent(mode, source) { + if (mode === 'yaml') { + return typeof source === 'string' ? yaml.safeLoad(source) : source; + } + if (mode === 'json') { + return typeof source === 'string' ? JSON.parse(source) : source; + } + return source; +} + + +function serializeContent(mode, source) { + if (mode === 'yaml') { + return yaml.safeDump(source); + } + if (mode === 'json') { + return JSON.stringify(source); + } + return source; +} + + +module.exports = function (source) { + try { + const resourceObject = path.parse(this.resourcePath); + const options = Object.assign({ + mode: 'yaml', + insertFileNameAsPrefix: isYAMLFile(resourceObject.ext) || isJSONFile(resourceObject.ext), + objectsDir: null + }, getOptions(this)); + + if (!options.objectsDir || !fs.existsSync(options.objectsDir)) { + return source; + } + + const overrides = {}; + fs.readdirSync(options.objectsDir).forEach((fileName) => { + const filePath = path.join(options.objectsDir, fileName); + const fileObject = path.parse(filePath); + + if (isYAMLFile(fileObject.ext) || isJSONFile(fileObject.ext)) { + overrides[fileObject.name] = parseContent( + isYAMLFile(fileObject.ext) ? 'yaml' : 'json', + fs.readFileSync(filePath, 'utf8') + ); + } + }); + + if (Object.keys(overrides).length === 0) { + return source; + } + + let content = {}; + if (options.insertFileNameAsPrefix) { + content = {[resourceObject.name]: parseContent(options.mode, source)}; + updateSourceObject(content, overrides); + content = content[resourceObject.name]; + } else { + content = parseContent(options.mode, source); + updateSourceObject(content, overrides); + } + + return serializeContent(options.mode, content); + } catch (err) { + this.emitError(err); + return source; + } +}; diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..38d8bef --- /dev/null +++ b/package-lock.json @@ -0,0 +1,95 @@ +{ + "name": "@infermedica/object-override-loader", + "version": "0.1.0", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "requires": { + "sprintf-js": "~1.0.2" + } + }, + "big.js": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", + "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==" + }, + "emojis-list": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-2.1.0.tgz", + "integrity": "sha1-TapNnbAPmBmIDHn6RXrlsJof04k=" + }, + "esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==" + }, + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" + }, + "js-yaml": { + "version": "3.13.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz", + "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==", + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + } + }, + "json5": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", + "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", + "requires": { + "minimist": "^1.2.0" + } + }, + "loader-utils": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.2.3.tgz", + "integrity": "sha512-fkpz8ejdnEMG3s37wGL07iSBDg99O9D5yflE9RGNH3hRdx9SOwYfnGYdZOUIZitN8E+E2vkq3MUMYMvPYl5ZZA==", + "requires": { + "big.js": "^5.2.2", + "emojis-list": "^2.0.0", + "json5": "^1.0.1" + } + }, + "minimist": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=" + }, + "path": { + "version": "0.12.7", + "resolved": "https://registry.npmjs.org/path/-/path-0.12.7.tgz", + "integrity": "sha1-1NwqUGxM4hl+tIHr/NWzbAFAsQ8=", + "requires": { + "process": "^0.11.1", + "util": "^0.10.3" + } + }, + "process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha1-czIwDoQBYb2j5podHZGn1LwW8YI=" + }, + "sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=" + }, + "util": { + "version": "0.10.4", + "resolved": "https://registry.npmjs.org/util/-/util-0.10.4.tgz", + "integrity": "sha512-0Pm9hTQ3se5ll1XihRic3FDIku70C+iHUdT/W926rSgHV5QgXsYbKZN8MSC3tJtSkhuROzvsQjAaFENRXr+19A==", + "requires": { + "inherits": "2.0.3" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..70cb5c8 --- /dev/null +++ b/package.json @@ -0,0 +1,27 @@ +{ + "name": "@infermedica/object-override-loader", + "description": "Object YAML/JSON override loader", + "version": "0.1.0", + "author": "Infermedica", + "license": "MIT", + "main": "index.js", + "repository": { + "type": "git", + "url": "git+ssh://git@github.com/infermedica/object-override-loader" + }, + "files": [ + "*.js", + "README.md" + ], + "keywords": [ + "i18n", + "loader", + "vue", + "webpack" + ], + "dependencies": { + "js-yaml": "^3.12.2", + "loader-utils": "^1.2.3", + "path": "^0.12.7" + } +}