├── .editorconfig ├── .gitignore ├── .travis.yml ├── CHANGELOG.md ├── LICENSE ├── README.md ├── config ├── jsonSerializer.js └── stringSerializer.js ├── docs ├── workflow.png └── workflow.sketch ├── package.json ├── renovate.json ├── src ├── cli.js ├── extractAndWritePOTFromMessagesSync.js ├── filterPOAndWriteTranslateSync.js ├── index.js ├── potFormater.js ├── potHeader.js ├── readAllMessageAsObjectSync.js ├── readAllPOAsObjectSync.js └── utils │ └── po2jsonHelper.js ├── test ├── __snapshots__ │ ├── extractAndWritePOTFromMessagesSync.test.js.snap │ ├── filterPOAndWriteTranslateSync.test.js.snap │ ├── potFormater.test.js.snap │ ├── potHeader.test.js.snap │ ├── readAllMessageAsObjectSync.test.js.snap │ └── readAllPOAsObjectSync.test.js.snap ├── alternate_po │ ├── zh_CN │ │ └── LC_MESSAGES │ │ │ └── messages.po │ └── zh_TW │ │ └── LC_MESSAGES │ │ └── messages.po ├── extractAndWritePOTFromMessagesSync.test.js ├── filterPOAndWriteTranslateSync.test.js ├── messages │ └── src │ │ └── containers │ │ ├── App │ │ └── App.json │ │ └── NotFound │ │ └── messages.json ├── po │ ├── mcs-ctxt.zh-CN.po │ ├── mcs-ctxt.zh-TW.po │ ├── mcs-id.zh-CN.po │ ├── mcs-id.zh-TW.po │ ├── mcs-public.zh-CN.po │ ├── mcs-public.zh-TW.po │ ├── zh-CN_project.po │ └── zh-TW_project.po ├── potFormater.test.js ├── potHeader.test.js ├── readAllMessageAsObjectSync.test.js ├── readAllPOAsObjectSync.test.js └── temp │ └── .gitkeep └── yarn.lock /.editorconfig: -------------------------------------------------------------------------------- 1 | # editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | [*.md] 13 | trim_trailing_whitespace = false 14 | 15 | [Makefile] 16 | indent_style = tab 17 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # OS X crap 2 | .DS_Store 3 | 4 | # no log 5 | *.log 6 | 7 | # Node.js / npm 8 | node_modules 9 | 10 | # build 11 | lib 12 | 13 | # code coverage 14 | .nyc_output 15 | coverage/ 16 | 17 | # test temp file 18 | test/temp/* 19 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: node_js 3 | node_js: 4 | - "6" 5 | - "8" 6 | - "9" 7 | 8 | env: 9 | global: 10 | - YARN_VERSION=1.5.1 11 | 12 | before_install: 13 | - export PATH="$HOME/.yarn/bin:$PATH" 14 | - | 15 | if [[ ! -e ~/.yarn/bin/yarn || $(yarn --version) != "${YARN_VERSION}" ]]; then 16 | curl -o- -L https://yarnpkg.com/install.sh | bash -s -- --version $YARN_VERSION 17 | fi 18 | 19 | install: 20 | - yarn install --pure-lockfile 21 | 22 | script: 23 | - yarn run eslint 24 | - yarn run test:coverage 25 | 26 | after_success: npm run codecov 27 | 28 | deploy: 29 | - provider: npm 30 | email: evenchange4@gmail.com 31 | api_key: 32 | secure: F1tJf6xnxw8MJy1STJqdZORZYI8gdOh2csxkktQstT6kztUtggYRtdtnzcAEsvNDhcWadI5D8mgvEd8g2ZvaK9u1bTDhFmM7zDJYzZkTNxFEdBs+xBfetOkjrHzEAthocRNKzja8PVrcxhCVuiabymhiZZkt/+2Diw1pxLRq1BeCtmFodZfyew5DDJqQn36R0+mxh19JaIxiNU2HglDCpdrpGLhCIDpnDzgIdRG6YPH2NA3Bft4hsZdEDtvnkXL4rDOOD2YBO6/RE4di6NIpOYYo9mvKlwX8968gfi56QqEBlytlgP6bl8Zh2kVPfLnrW2P0f4xELi03oZIBtiNYhaTryQOMLXe+9Nn8QxM+IerllslTfy7rX17JmHrbgPvqfZMH3b144CJbWkrCuPrVRQXQFg6TNiwgd6e54/lSLZFKJkcrsr3VCEWLpXwoj8ojQ5ELs0RvQXW/OWHsAsmLRMMI1QGlVZvCpPU/Nt0dz5rEsXYiyc1ITC4mf/mJBgMnjD/wCKlIrvgN6r26YsIdp1Vzt244gxXPQCmR+epZ4Pwa25xFzTNyn3NchgZNfYzhqiXBxeCf7Y2dMSPgqKB++TrgJUctK/fxcr6d+7KsduydZ2A5FKxo/rLnRDJgdsDa5QbsYwPRY8k60ABSs3na9Wcj+nVCNVFQvnGAk7E/dTY= 33 | skip_cleanup: true 34 | on: 35 | tags: true 36 | repo: evenchange4/react-intl-po 37 | 38 | cache: 39 | yarn: true 40 | directories: 41 | - "~/.yarn" 42 | - node_modules 43 | 44 | notifications: 45 | email: evenchange4@gmail.com 46 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # react-intl-po 2 | 3 | ## [HEAD] 4 | 5 | > Unreleased 6 | 7 | ## [v2.2.2] 8 | 9 | > April 30, 2018 10 | 11 | * feat: Add sort-by-id option to po2json([@matheusd](https://github.com/matheusd) in [#134]) 12 | * chore: ncu update 13 | 14 | ## [v2.2.1] 15 | 16 | > April 11, 2018 17 | 18 | * feat: Support --indentation option ([@matheusd](https://github.com/matheusd) in [#128]) 19 | * chore: ncu update & use node 9.11.1 20 | 21 | ## [v2.2.0] 22 | 23 | > March 13, 2018 24 | 25 | * feat: Custom lang mapper regex and index ([@camflan](https://github.com/camflan) in [#122]) 26 | * chore: ncu update & upgrade node 9.8.0 and yarn 1.5.1 27 | 28 | ## [v2.1.3] 29 | 30 | > Jan 26, 2018 31 | 32 | * fix(dependency): pinOnlyDevDependencies (#111) 33 | 34 | ## [v2.1.2] 35 | 36 | > Jan 16, 2018 37 | 38 | * chore: ncu update 39 | 40 | ## [v2.1.1] 41 | 42 | > Jul 19, 2017 43 | 44 | * chore(env): add node 8 to travisCI & switch to yarn 45 | * chore(dev): introduce prettier & switch from eslint-config-m to airbnb-base 46 | * chore(test): upgrade Jest to 20 47 | * refactor(ramda): switch from lodash to ramda.js 48 | * fix(context): missing the `messageContext` arguments for extractAndWritePOTFromMessagesSync function. 49 | 50 | ## [v2.1.0] 51 | 52 | > Jul 17, 2017 53 | 54 | * feat(contexts): Allow user to specify msgctxt with `-c` arguments. ([@Sand1929](https://github.com/Sand1929)in [#84]) 55 | 56 | Example: https://github.com/evenchange4/react-intl-po-example#option 57 | 58 | ## [v2.0.2] 59 | 60 | > Mar 02, 2017 61 | 62 | * fix(potFormater): Escape quotes in msgId ([@jonbretman](https://github.com/jonbretman) in [#70]) 63 | * chore(Jest): update jest to 19. 64 | 65 | ## [v2.0.1] 66 | 67 | > Feb 12, 2017 68 | 69 | * feat(potFormater): Add defaultMessage to metadata of pot files. ([@Guibod](https://github.com/Guibod) in [#60]) 70 | 71 | ## [v2.0.0] 72 | 73 | > Feb 11, 2017 74 | 75 | * feat(jest): replace ava with jest snapshot testing (#63) 76 | * fix(messageKey): allowing duplicated key in the same file. (#59) 77 | 78 | [BREAKING CHANGES] 79 | 80 | * readAllMessageAsObjectSync: Passing `messageKey: String` as second parameter. 81 | 82 | ## [v1.2.0] 83 | 84 | > Feb 11, 2017 85 | 86 | * feat(pot): Added support for POT header. ([@Guibod](https://github.com/Guibod) in [#56]) 87 | 88 | ## [v1.1.0] 89 | 90 | > Oct 26, 2016 91 | 92 | * feat(node): upgrade to node v6 93 | * feat(npm): upgrade to npm v3 94 | * feat(eslint): upgrade to eslint v3 95 | 96 | ## [v1.0.10] 97 | 98 | > Oct 21, 2016 99 | 100 | * feat(messageKey): Added feature where you can pass the message key and use something else than defaultMessage. ([@janzal](https://github.com/janzal) in [#41]) 101 | 102 | ## [v1.0.9] 103 | 104 | > Sep 21, 2016 105 | 106 | * fix(DEFAULT_MAPPER): prefix is not required anymore. ([@eliseumds](https://github.com/eliseumds) in [#19], [@MorrisGallego](https://github.com/MorrisGallego) in [#34]) 107 | 108 | ## [v1.0.7] 109 | 110 | > Sep 13, 2016 111 | 112 | * test(structure): move output .temp file into the temp folder for gitignore. 113 | * feat(po2json): added generation of one output file per input if the specified output is a folder. ([@MorrisGallego](https://github.com/MorrisGallego) in [#29]) 114 | 115 | ## [v1.0.6] 116 | 117 | > Aug 19, 2016 118 | 119 | * chore(modules): upgrade dependency 120 | * feat(filterPOAndWriteTranslateSync): ensure the output folder exists. ([@wuct](https://github.com/wuct) in [#27]) 121 | 122 | ## [v1.0.4] 123 | 124 | > Apr 12, 2016 125 | 126 | * chore(ava): update lint and test 127 | * chore(codecov): switch to use codecov 128 | * chore(alias): use full name of package 129 | 130 | ## [v1.0.1] 131 | 132 | > Mar 30, 2016 133 | 134 | * feat(cli): add cli bin config 135 | 136 | ## [v1.0.0] 137 | 138 | > Mar 27, 2016 139 | 140 | * first release 141 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2018 Michael Hsu 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # react-intl-po 2 | 3 | > Extract POT from react-intl and convert back to json. 4 | > 5 | > messages.json → POT → PO → translation.json 6 | 7 | [![Travis][build-badge]][build] 8 | [![Codecov Status][codecov-badge]][codecov] 9 | [![npm package][npm-badge]][npm] 10 | [![npm downloads][npm-downloads]][npm] 11 | 12 | [![Dependency Status][dependency-badge]][dependency] 13 | [![devDependency Status][devdependency-badge]][devdependency] 14 | [![peerDependency Status][peerdependency-badge]][peerdependency] 15 | 16 | [![license][license-badge]][license] 17 | [![prettier](https://img.shields.io/badge/styled_with-prettier-ff69b4.svg)](https://github.com/prettier/prettier) 18 | 19 | ## Tutorial 20 | 21 | Please take a look at standalone repo based on Create-React-App: https://github.com/evenchange4/react-intl-po-example 22 | 23 | ## Installation 24 | 25 | ```sh 26 | $ yarn add react-intl-po 27 | ``` 28 | 29 | ## Requirements and Workflow 30 | 31 | * [react-intl](https://github.com/yahoo/react-intl) `^2.0.0` 32 | * [babel-plugin-react-intl](https://github.com/yahoo/babel-plugin-react-intl) `^2.0.0` 33 | 34 | ![RIP Workflow](./docs/workflow.png) 35 | 36 | ## Usage 37 | 38 | There are two sub-commands of `react-intl-po` or `rip`: 39 | 40 | 1. json2pot: Convert the json files extracted from _babel-plugin-react-intl_ into one `.pot` file. 41 | 2. po2json: Convert translated _.po_ files back to `.json` format. 42 | 43 | ### json2pot 44 | 45 | ```sh 46 | $ rip json2pot '_translations/src/**/*.json' \ 47 | -o ./mcs-public.pot 48 | ``` 49 | 50 | | **Arguments** | **Description** | 51 | | --------------------------------- | --------------------------------------------------------------------- | 52 | | `srcPatterns` | The pattern of _.json_ files extracted from _babel-plugin-react-intl_ | 53 | | `-o, --output ` | The output pathname of _.pot_ file to be translated | 54 | | `-k, --message-key [key]` | [Optional] Translation message key (default key is `defaultMessage`) | 55 | | `-c, --message-context [context]` | [Optional] Translation message context (defaults to no context) | 56 | 57 | ### po2json 58 | 59 | #### Case 1: Output one file per locale if a `directory` is set 60 | 61 | ```sh 62 | $ rip po2json './node_modules/mcs-translation/po/mcs-public*.po' \ 63 | -m './_translations/src/**/*.json' \ 64 | -o './translations' 65 | ``` 66 | 67 | #### Case 2: Output one merged file if a `.json file` is set 68 | 69 | ```sh 70 | $ rip po2json './node_modules/mcs-translation/po/mcs-public*.po' \` 71 | -m './_translations/src/**/*.json' \ 72 | -o './translations.json' 73 | ``` 74 | 75 | | **Arguments** | **Description** | 76 | | ----------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | 77 | | `srcPatterns` | The pattern of translated _.po_ files | 78 | | `-m, --messages-pattern ` | The pattern of _.json_ files extracted from _babel-plugin-react-intl_ | 79 | | `-o, --output ` | The output pathname of a file / directory | 80 | | `-k, --message-key [key]` | [Optional] Translation message key (default key is `defaultMessage`) | 81 | | `-c, --message-context [context]` | [Optional] Translation message context (defaults to no context) | 82 | | `-l, --lang-mapper-pattern ` | [Optional] Custom regex to use for lang mapping. [PR#122](https://github.com/evenchange4/react-intl-po/pull/122) | 83 | | `-i, --lang-mapper-pattern-index [index]` | [Optional] When specifying a custom lang-mapper-pattern, the index of match to use for the lang mapping. Default is 1, index is ignored if not using a custom lang mapping regex. [PR#122](https://github.com/evenchange4/react-intl-po/pull/122) | 84 | | `--indentation ` | [Optional] Specify a number of spaces or a set of characters to be used before each entry of the resulting json file. Defaults to `null`, which means the entries are not prefixed with spaces. | 85 | | `--sort-by-id` | [Optional] If specified, the entries of each language are sorted by id before being output | 86 | 87 | ## Property 88 | 89 | ## Q&A 90 | 91 | ### How to translate the same message into two different meanings? 92 | 93 | #### Option 1 (Recommended): 94 | 95 | Set the `message-context (-c)` to `'id'` of message object from _babel-plugin-react-intl_ (there is no context by default). 96 | 97 | The advantage of this option over Option 2 (below) is that PO file editors that provide features such as translation suggestions or error-checking often expect the message key to be `defaultMessage`. 98 | 99 | ```sh 100 | $ rip po2json './node_modules/mcs-translation/po/mcs-public*.po' \ 101 | -m './_translations/src/**/*.json' \ 102 | -o './translations' \ 103 | -c 'id' 104 | 105 | $ rip po2json './node_modules/mcs-translation/po/mcs-public*.po' \` 106 | -m './_translations/src/**/*.json' \ 107 | -o './translations.json' \ 108 | -c 'id' 109 | ``` 110 | 111 | Example: https://github.com/evenchange4/react-intl-po-example#option 112 | 113 | #### Option 2: [Maybe deprecated next major release] 114 | 115 | Set the `message-key (-k)` to `'id'` of message object from _babel-plugin-react-intl_ (default key is `'defaultMessage'`). ([#41](https://github.com/evenchange4/react-intl-po/pull/41)) 116 | 117 | ```sh 118 | $ rip po2json './node_modules/mcs-translation/po/mcs-public*.po' \ 119 | -m './_translations/src/**/*.json' \ 120 | -o './translations' \ 121 | -k 'id' 122 | 123 | $ rip po2json './node_modules/mcs-translation/po/mcs-public*.po' \` 124 | -m './_translations/src/**/*.json' \ 125 | -o './translations.json' \ 126 | -k 'id' 127 | ``` 128 | 129 | ## Development 130 | 131 | ```sh 132 | $ yarn install --pure-lockfile 133 | ``` 134 | 135 | ### Ramda.js 136 | 137 | You can use `R.tap()` for developing. 138 | 139 | ```diff 140 | R.pipe( 141 | R.concat(...), 142 | + R.tap(e => console.log(e)), 143 | R.mergeAll, 144 | ); 145 | ``` 146 | 147 | ### Requirements 148 | 149 | * node >= 9.11.1 150 | * yarn >= 1.5.1 151 | 152 | ### Test 153 | 154 | ```sh 155 | $ yarn run format 156 | $ yarn run eslint 157 | $ yarn run test:watch 158 | ``` 159 | 160 | ### NPM Release 161 | 162 | > Any git tags. 163 | 164 | 1. Create a new git tag 165 | 2. Update `CHANGELOG.md` 166 | 167 | ```sh 168 | $ npm version patch 169 | ``` 170 | 171 | --- 172 | 173 | ## CONTRIBUTING 174 | 175 | * ⇄ Pull requests and ★ Stars are always welcome. 176 | * For bugs and feature requests, please create an issue. 177 | * Pull requests must be accompanied by passing automated tests (`$ yarn run test`). 178 | 179 | ## [CHANGELOG](CHANGELOG.md) 180 | 181 | ## [LICENSE](LICENSE) 182 | 183 | MIT: [http://michaelhsu.mit-license.org](http://michaelhsu.mit-license.org) 184 | 185 | [build-badge]: https://img.shields.io/travis/evenchange4/react-intl-po/master.svg?style=flat-square 186 | [build]: https://travis-ci.org/evenchange4/react-intl-po 187 | [npm-badge]: https://img.shields.io/npm/v/react-intl-po.svg?style=flat-square 188 | [npm]: https://www.npmjs.org/package/react-intl-po 189 | [codecov-badge]: https://img.shields.io/codecov/c/github/evenchange4/react-intl-po.svg?style=flat-square 190 | [codecov]: https://codecov.io/github/evenchange4/react-intl-po?branch=master 191 | [npm-downloads]: https://img.shields.io/npm/dt/react-intl-po.svg?style=flat-square 192 | [license-badge]: https://img.shields.io/npm/l/react-intl-po.svg?style=flat-square 193 | [license]: http://michaelhsu.mit-license.org/ 194 | [dependency-badge]: https://david-dm.org/evenchange4/react-intl-po.svg?style=flat-square 195 | [dependency]: https://david-dm.org/evenchange4/react-intl-po 196 | [devdependency-badge]: https://david-dm.org/evenchange4/react-intl-po/dev-status.svg?style=flat-square 197 | [devdependency]: https://david-dm.org/evenchange4/react-intl-po#info=devDependencies 198 | [peerdependency-badge]: https://david-dm.org/evenchange4/react-intl-po/peer-status.svg?style=flat-square 199 | [peerdependency]: https://david-dm.org/evenchange4/react-intl-po#info=peerDependencies 200 | -------------------------------------------------------------------------------- /config/jsonSerializer.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | test: val => val && typeof val === 'object', 3 | print: val => JSON.stringify(val, null, 2), 4 | }; 5 | -------------------------------------------------------------------------------- /config/stringSerializer.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | test: val => val && typeof val === 'string', 3 | print: val => val, 4 | }; 5 | -------------------------------------------------------------------------------- /docs/workflow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/evenchange4/react-intl-po/85313cd481b72de481e4d07526397339821bfa70/docs/workflow.png -------------------------------------------------------------------------------- /docs/workflow.sketch: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/evenchange4/react-intl-po/85313cd481b72de481e4d07526397339821bfa70/docs/workflow.sketch -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-intl-po", 3 | "version": "2.2.2", 4 | "description": "Extract POT from react-intl and convert back to json.", 5 | "author": "Michael Hsu", 6 | "license": "MIT", 7 | "bugs": { 8 | "url": "https://github.com/evenchange4/react-intl-po/issues" 9 | }, 10 | "homepage": "https://github.com/evenchange4/react-intl-po#readme", 11 | "repository": { 12 | "type": "git", 13 | "url": "https://github.com/evenchange4/react-intl-po.git" 14 | }, 15 | "main": "lib/index.js", 16 | "bin": { 17 | "rip": "lib/cli.js", 18 | "react-intl-po": "lib/cli.js" 19 | }, 20 | "files": ["lib", "src", "test"], 21 | "keywords": ["react-intl", "babel-plugin-react-intl", "po", "pot"], 22 | "scripts": { 23 | "clean": "rm -rf lib", 24 | "prebuild": "npm run clean", 25 | "build": "NODE_ENV=production babel src --out-dir lib", 26 | "test": "jest", 27 | "test:watch": "npm run test -- --watch --coverage", 28 | "test:coverage": "npm run test -- --coverage", 29 | "codecov": "codecov", 30 | "prepublish": "npm run build", 31 | "eslint": "eslint --ignore-path .gitignore .", 32 | "format": "prettier --write '{src,test,config}/**/*.js' '*.{md,json}'", 33 | "precommit": "lint-staged" 34 | }, 35 | "devDependencies": { 36 | "babel-cli": "6.26.0", 37 | "babel-eslint": "8.2.3", 38 | "babel-plugin-add-module-exports": "0.2.1", 39 | "babel-preset-es2015": "6.24.1", 40 | "babel-preset-stage-0": "6.24.1", 41 | "codecov": "3.0.2", 42 | "eslint": "4.19.1", 43 | "eslint-config-airbnb-base": "12.1.0", 44 | "eslint-config-prettier": "2.9.0", 45 | "eslint-plugin-import": "2.11.0", 46 | "eslint-plugin-jest": "21.15.1", 47 | "eslint-plugin-prettier": "2.6.0", 48 | "husky": "0.14.3", 49 | "jest": "22.4.3", 50 | "lint-staged": "7.1.0", 51 | "prettier": "1.12.1" 52 | }, 53 | "babel": { 54 | "presets": ["es2015", "stage-0"], 55 | "plugins": ["add-module-exports"] 56 | }, 57 | "jest": { 58 | "testEnvironment": "node", 59 | "testRegex": "(/test/.*|\\.(test))\\.js$", 60 | "moduleFileExtensions": ["js"], 61 | "snapshotSerializers": [ 62 | "./config/stringSerializer.js", 63 | "./config/jsonSerializer.js" 64 | ] 65 | }, 66 | "prettier": { 67 | "trailingComma": "all", 68 | "singleQuote": true 69 | }, 70 | "lint-staged": { 71 | "*.{js,json,md}": ["prettier --write", "git add"] 72 | }, 73 | "eslintConfig": { 74 | "parser": "babel-eslint", 75 | "extends": [ 76 | "eslint-config-airbnb-base", 77 | "eslint-config-airbnb-base/rules/strict", 78 | "prettier", 79 | "prettier/flowtype", 80 | "plugin:jest/recommended" 81 | ], 82 | "plugins": ["prettier", "jest"], 83 | "env": { 84 | "jest/globals": true 85 | }, 86 | "rules": { 87 | "import/no-extraneous-dependencies": 0, 88 | "prettier/prettier": ["error"] 89 | } 90 | }, 91 | "dependencies": { 92 | "chalk": "^2.3.2", 93 | "commander": "^2.15.1", 94 | "glob": "^7.1.2", 95 | "mkdirp": "^0.5.1", 96 | "po2json": "^0.4.5", 97 | "ramda": "^0.25.0" 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["config:js-lib"] 3 | } 4 | -------------------------------------------------------------------------------- /src/cli.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | import program from 'commander'; 4 | 5 | const numberOrChars = s => (/^\d+$/.test(s) ? parseInt(s, 10) : s); 6 | 7 | program 8 | .command('json2pot ') 9 | .option( 10 | '-o, --output ', 11 | 'The output pathname of `.pot` file to be translated', 12 | ) 13 | .option( 14 | '-k, --message-key [key]', 15 | 'Translation message key (default key is `defaultMessage`)', 16 | ) 17 | .option( 18 | '-c, --message-context [context]', 19 | 'Translation message context (defaults to no context)', 20 | ) 21 | .action(require('./extractAndWritePOTFromMessagesSync')); 22 | 23 | program 24 | .command('po2json ') 25 | .option( 26 | '-m, --messages-pattern ', 27 | 'The pattern of *json* files extracted from *babel-plugin-react-intl*', 28 | ) 29 | .option('-o, --output ', 'The output pathname of a file / directory') 30 | .option( 31 | '-k, --message-key [key]', 32 | 'Translation message key (default key is `defaultMessage`)', 33 | ) 34 | .option( 35 | '-c, --message-context [context]', 36 | 'Translation message context (defaults to no context)', 37 | ) 38 | .option( 39 | '-l, --lang-mapper-pattern ', 40 | 'Custom regex to use for lang mapping.', 41 | ) 42 | .option( 43 | '-i, --lang-mapper-pattern-index [index]', 44 | 'When specifying a custom lang-mapper-pattern, the index of match to use for the lang mapping. Default is 1, index is ignored if not using a custom lang mapping regex', 45 | ) 46 | .option( 47 | '--indentation ', 48 | 'Number of spaces or characters to use for indenting (adding space) to the output json entries.', 49 | numberOrChars, 50 | ) 51 | .option( 52 | '--sort-by-id', 53 | 'If specified, the entries of each language are sorted by id before being output', 54 | ) 55 | .action(require('./filterPOAndWriteTranslateSync')); 56 | 57 | program.parse(process.argv); 58 | -------------------------------------------------------------------------------- /src/extractAndWritePOTFromMessagesSync.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-console */ 2 | 3 | import fs from 'fs'; 4 | import chalk from 'chalk'; 5 | import * as R from 'ramda'; 6 | import readAllMessageAsObjectSync from './readAllMessageAsObjectSync'; 7 | import potFormater from './potFormater'; 8 | import potHeader from './potHeader'; 9 | 10 | function extractAndWritePOTFromMessagesSync( 11 | srcPatterns, 12 | { messageKey = 'defaultMessage', messageContext = '', output, headerOptions }, 13 | ) { 14 | const result = R.pipe( 15 | readAllMessageAsObjectSync, 16 | // 1. Object { messagekey: { messageContext: [[] , []] } } 17 | potFormater, 18 | // 2. String: pot formated 19 | R.concat( 20 | potHeader({ 21 | potCreationDate: new Date(), 22 | charset: 'UTF-8', 23 | encoding: '8bit', 24 | ...headerOptions, 25 | }), 26 | ), 27 | // 3. String: with pot head 28 | )(srcPatterns, messageKey, messageContext); 29 | 30 | // Output 31 | fs.writeFileSync(output, result); 32 | console.log(chalk.green(`> [react-intl-po] write file -> ${output} ✔️\n`)); 33 | } 34 | 35 | export default extractAndWritePOTFromMessagesSync; 36 | -------------------------------------------------------------------------------- /src/filterPOAndWriteTranslateSync.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-console */ 2 | 3 | import fs from 'fs'; 4 | import path from 'path'; 5 | import mkdirp from 'mkdirp'; 6 | import * as R from 'ramda'; 7 | import chalk from 'chalk'; 8 | import readAllMessageAsObjectSync from './readAllMessageAsObjectSync'; 9 | import readAllPOAsObjectSync from './readAllPOAsObjectSync'; 10 | 11 | const isAJSONFile = string => /.json/.test(string); 12 | 13 | const getContext = messageContext => message => 14 | messageContext ? `${message[messageContext]}\u0004` : ''; 15 | 16 | const makeLangMapper = (pattern, index = 1) => filepath => 17 | filepath.match(pattern)[index]; 18 | 19 | function filterPOAndWriteTranslateSync( 20 | srcPatterns, 21 | { 22 | messageKey = 'defaultMessage', 23 | messageContext = '', 24 | messagesPattern, 25 | langMapperPattern, 26 | langMapperPatternIndex, 27 | output, 28 | indentation, 29 | sortById, 30 | }, 31 | ) { 32 | const sort = sortById ? R.sortBy(R.prop('id')) : R.identity; 33 | 34 | const placeholder = R.pipe( 35 | readAllMessageAsObjectSync, 36 | // 1. Object { messagekey: { messageContext: [[] , []] } } 37 | R.values, 38 | // 2. Array [{ messageContext: [[], []] }] 39 | R.map(R.values), 40 | // 3. Array [[], []] 41 | R.flatten, 42 | sort, 43 | // 4. Array [] 44 | R.indexBy(R.prop('id')), 45 | // 5. Object { id: [] } 46 | R.mapObjIndexed( 47 | R.converge(R.concat, [getContext(messageContext), R.prop(messageKey)]), 48 | ), 49 | // 6. Object { id: key }, key = (messageContext + messagekey) 50 | )(messagesPattern, messageKey, messageContext); 51 | 52 | const langMapperFn = 53 | langMapperPattern !== undefined 54 | ? makeLangMapper(langMapperPattern, langMapperPatternIndex) 55 | : undefined; 56 | 57 | const result = R.pipe( 58 | readAllPOAsObjectSync, 59 | // 1. Object { locale: { key: '' } } 60 | translation => 61 | Object.keys(translation).map(locale => ({ 62 | [locale]: R.mapObjIndexed(k => translation[locale][k] || '')( 63 | placeholder, 64 | ), 65 | })), 66 | // 2. Array [{ locale: { id: '' } }], replace key to translated string 67 | R.mergeAll, 68 | // 3. Object { locale: { id: '' } } 69 | )(srcPatterns, langMapperFn); 70 | 71 | // Output 72 | if (isAJSONFile(output)) { 73 | mkdirp.sync(path.dirname(output)); // ensure the output folder exists 74 | fs.writeFileSync(output, JSON.stringify(result, null, indentation)); 75 | console.log(chalk.green(`> [react-intl-po] write file -> ${output} ✔️\n`)); 76 | } else { 77 | mkdirp.sync(output); // ensure the output folder exists 78 | 79 | Object.keys(result).forEach(lang => { 80 | fs.writeFileSync( 81 | path.join(output, `${lang}.json`), 82 | JSON.stringify(result[lang], null, indentation), 83 | ); 84 | console.log( 85 | chalk.green( 86 | `> [react-intl-po] write file -> ${path.join( 87 | output, 88 | `${lang}.json`, 89 | )} ✔️`, 90 | ), 91 | ); 92 | }); 93 | } 94 | } 95 | 96 | export default filterPOAndWriteTranslateSync; 97 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import extractAndWritePOTFromMessagesSync from './extractAndWritePOTFromMessagesSync'; 2 | import filterPOAndWriteTranslateSync from './filterPOAndWriteTranslateSync'; 3 | import potFormater from './potFormater'; 4 | import potHeader from './potHeader'; 5 | import readAllMessageAsObjectSync from './readAllMessageAsObjectSync'; 6 | import readAllPOAsObjectSync from './readAllPOAsObjectSync'; 7 | 8 | export default { 9 | extractAndWritePOTFromMessagesSync, 10 | filterPOAndWriteTranslateSync, 11 | potFormater, 12 | potHeader, 13 | readAllMessageAsObjectSync, 14 | readAllPOAsObjectSync, 15 | }; 16 | -------------------------------------------------------------------------------- /src/potFormater.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Ensure that multi-line strings are properly commented out 3 | * 4 | * For instance: 5 | * This is\nmy multi-line\ncomment 6 | * 7 | * should be escaped as : 8 | * #. This is 9 | * #. my multi-line 10 | * #. comment 11 | * 12 | * @param {String} commentPrefix 13 | * @param {String} rawComment 14 | * @returns {String} 15 | * 16 | * @author Guillaume Boddaert 17 | */ 18 | const potCommentMultiLineWrapper = (commentPrefix, rawComment) => { 19 | const comments = rawComment.split('\n'); 20 | return comments.reduce((a, b) => `${a}${commentPrefix} ${b}\n`, ''); 21 | }; 22 | 23 | /** 24 | * Formatting POT comments 25 | * @param {Object[]} messageList 26 | * @return {String} 27 | * 28 | * example: see tests 29 | * 30 | * @author Michael Hsu 31 | * @author Guillaume Boddaert 32 | */ 33 | const potCommentsFormater = messageList => 34 | messageList.reduce((acc, { filename, id, description, defaultMessage }) => { 35 | let out = acc; 36 | out += potCommentMultiLineWrapper('#:', filename); 37 | if (description) { 38 | out += potCommentMultiLineWrapper('#.', `[${id}] - ${description}`); 39 | } else { 40 | out += potCommentMultiLineWrapper('#.', `[${id}]`); 41 | } 42 | out += potCommentMultiLineWrapper( 43 | '#.', 44 | `defaultMessage is:\n${defaultMessage}`, 45 | ); 46 | 47 | return out; 48 | }, ''); 49 | 50 | /** 51 | * Formatting POT contexts 52 | * @param {String} messageContext 53 | * @return {String} 54 | * 55 | * @author Sandy Suh 56 | */ 57 | const potContextsFormater = messageContext => 58 | messageContext ? `msgctxt ${JSON.stringify(messageContext)}\n` : ''; 59 | 60 | /** 61 | * Formatting POT comments 62 | * @param {Object} messageObject 63 | * @return {String} 64 | * 65 | * example: see tests 66 | * 67 | * @author Michael Hsu 68 | */ 69 | 70 | const potFormater = messageObject => 71 | Object.keys(messageObject) // return array of id 72 | .sort() 73 | .map(id => 74 | Object.keys(messageObject[id]) 75 | .map( 76 | context => 77 | `${potCommentsFormater( 78 | messageObject[id][context], 79 | )}${potContextsFormater(context)}msgid ${JSON.stringify( 80 | id, 81 | )}\nmsgstr ""\n`, 82 | ) 83 | .join('\n'), 84 | ) 85 | .join('\n'); 86 | 87 | export default potFormater; 88 | -------------------------------------------------------------------------------- /src/potHeader.js: -------------------------------------------------------------------------------- 1 | import * as R from 'ramda'; 2 | 3 | /** 4 | * Create a POT header string 5 | * @param {Object} options 6 | * @param {String|String[]} [options.comments] 7 | * @param {Date} [options.potCreationDate] used for POT-Creation-Date 8 | * @param {String} [options.projectIdVersion] Project-Id-Version 9 | * @param {String} [options.charset] 10 | * @param {String} [options.encoding] 11 | * @return {String} potSource 12 | * 13 | * example: see tests 14 | * 15 | * @see https://www.gnu.org/software/trans-coord/manual/gnun/html_node/PO-Header.html 16 | * @author Guillaume Boddaert 17 | */ 18 | 19 | const potHeader = (options = {}) => { 20 | const o = R.evolve({ 21 | comments: R.pipe( 22 | R.cond([[R.is(Array), R.identity], [R.is(String), R.of]]), 23 | R.map(R.split('\n')), 24 | R.flatten, 25 | R.map(e => `# ${e}`), 26 | R.join('\n'), 27 | ), 28 | projectIdVersion: e => `"Project-Id-Version: ${e}\\n"`, 29 | potCreationDate: e => `"POT-Creation-Date: ${e.toISOString()}\\n"`, 30 | charset: e => `"Content-Type: text/plain; charset=${e}\\n"`, 31 | encoding: e => `"Content-Transfer-Encoding: ${e}\\n"`, 32 | })(options); 33 | 34 | return `${o.comments} 35 | msgid "" 36 | msgstr "" 37 | ${o.projectIdVersion} 38 | ${o.potCreationDate} 39 | ${o.charset} 40 | ${o.encoding} 41 | "MIME-Version: 1.0\\n" 42 | "X-Generator: react-intl-po\\n" 43 | 44 | 45 | `.replace(/undefined\r?\n|\r/g, ''); 46 | }; 47 | 48 | export default potHeader; 49 | -------------------------------------------------------------------------------- /src/readAllMessageAsObjectSync.js: -------------------------------------------------------------------------------- 1 | import fs from 'fs'; 2 | import { sync as globSync } from 'glob'; 3 | import * as R from 'ramda'; 4 | 5 | /** 6 | * Read extracted .json file synchronized and 7 | * aggregates origin messages objects 8 | * 9 | * @param {String} srcPatterns - path to translated .json file 10 | * @param {String} messageKey - [defaultMessage] 11 | * @param {String} messageContext - [''] empty string 12 | * @return {Object} messages - return aggregates object 13 | * 14 | * @author Michael Hsu 15 | */ 16 | 17 | const readAllMessageAsObjectSync = ( 18 | srcPatterns, 19 | messageKey = 'defaultMessage', 20 | messageContext = '', 21 | ) => 22 | R.pipe( 23 | globSync, 24 | // 1. Array [filename, ...] 25 | R.map(filename => 26 | JSON.parse(fs.readFileSync(filename, 'utf8')).map(e => ({ 27 | ...e, 28 | filename, 29 | })), 30 | ), 31 | R.flatten, 32 | // 2. Array [{ ...messages, filename }, ... ] 33 | R.groupBy(R.prop(messageKey)), 34 | // 3. Object { messageKey: { }, ... } 35 | R.mapObjIndexed(R.groupBy(R.propOr(messageContext, messageContext))), 36 | // 4. Object { messagekey: { messageContext: { } } } 37 | // 4. groupBy context (nested for -c argument) 38 | )(srcPatterns); 39 | 40 | export default readAllMessageAsObjectSync; 41 | -------------------------------------------------------------------------------- /src/readAllPOAsObjectSync.js: -------------------------------------------------------------------------------- 1 | import { sync as globSync } from 'glob'; 2 | import path from 'path'; 3 | import * as R from 'ramda'; 4 | import po2jsonHelper from './utils/po2jsonHelper'; 5 | 6 | export const DEFAULT_MAPPER = filepath => 7 | path.basename(filepath).match(/([^.]*\.)*([^.]+)\.po$/)[2]; 8 | 9 | /** 10 | * Read translated .po file synchronized and 11 | * aggregates translated messages object 12 | * @param {String} srcPatterns - path to translated .po file 13 | * @return {Object} po - return aggregates object 14 | */ 15 | 16 | const readAllPOAsObjectSync = (srcPatterns, localeMapper = DEFAULT_MAPPER) => 17 | R.pipe( 18 | globSync, 19 | // 1. Array [filename, ...] 20 | R.map(R.converge(R.objOf, [localeMapper, po2jsonHelper.parseFileSync])), 21 | // 2. Array [{ locale: json }, ...] 22 | R.mergeAll, 23 | // 3. Object { locale: json } 24 | )(srcPatterns); 25 | 26 | export default readAllPOAsObjectSync; 27 | -------------------------------------------------------------------------------- /src/utils/po2jsonHelper.js: -------------------------------------------------------------------------------- 1 | import * as R from 'ramda'; 2 | import po2json from 'po2json'; 3 | 4 | const parseFileSync = R.pipe( 5 | // 1. Convert po to json format 6 | filename => po2json.parseFileSync(filename), 7 | // 2. Omit pot epmty head above 8 | R.omit(['']), 9 | // 3. Omit plural 10 | R.mapObjIndexed(R.nth(1)), 11 | ); 12 | 13 | export default { 14 | parseFileSync, 15 | }; 16 | -------------------------------------------------------------------------------- /test/__snapshots__/extractAndWritePOTFromMessagesSync.test.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`should return messages object with custom message key mapper 1`] = ` 4 | msgid "" 5 | msgstr "" 6 | "POT-Creation-Date: 2017-02-01T11:23:12.000Z\\n" 7 | "Content-Type: text/plain; charset=UTF-8\\n" 8 | "Content-Transfer-Encoding: 8bit\\n" 9 | "MIME-Version: 1.0\\n" 10 | "X-Generator: react-intl-po\\n" 11 | 12 | 13 | #: ./test/messages/src/containers/App/App.json 14 | #. [App.Creator 1] - Creator 1 15 | #. defaultMessage is: 16 | #. Creator 17 | msgid "App.Creator 1" 18 | msgstr "" 19 | 20 | #: ./test/messages/src/containers/App/App.json 21 | #. [App.Creator 2] - Creator 2 22 | #. defaultMessage is: 23 | #. Creator 24 | msgid "App.Creator 2" 25 | msgstr "" 26 | 27 | #: ./test/messages/src/containers/App/App.json 28 | #. [App.errorButton] - Click error Button 29 | #. defaultMessage is: 30 | #. Go to MCS website 31 | msgid "App.errorButton" 32 | msgstr "" 33 | 34 | #: ./test/messages/src/containers/App/App.json 35 | #. [App.errorMessage] - The error message when api response as 404 not found 36 | #. defaultMessage is: 37 | #. The device is now private or deleted. 38 | msgid "App.errorMessage" 39 | msgstr "" 40 | 41 | #: ./test/messages/src/containers/NotFound/messages.json 42 | #. [NotFound.Creator] - Creator 43 | #. defaultMessage is: 44 | #. Creator 45 | msgid "NotFound.Creator" 46 | msgstr "" 47 | 48 | #: ./test/messages/src/containers/NotFound/messages.json 49 | #. [NotFound.errorButton] - Click error Button 50 | #. defaultMessage is: 51 | #. Go to MCS website 52 | msgid "NotFound.errorButton" 53 | msgstr "" 54 | 55 | `; 56 | 57 | exports[`should return messages object with default mapper 1`] = ` 58 | msgid "" 59 | msgstr "" 60 | "POT-Creation-Date: 2017-02-01T11:23:12.000Z\\n" 61 | "Content-Type: text/plain; charset=UTF-8\\n" 62 | "Content-Transfer-Encoding: 8bit\\n" 63 | "MIME-Version: 1.0\\n" 64 | "X-Generator: react-intl-po\\n" 65 | 66 | 67 | #: ./test/messages/src/containers/App/App.json 68 | #. [App.Creator 1] - Creator 1 69 | #. defaultMessage is: 70 | #. Creator 71 | #: ./test/messages/src/containers/App/App.json 72 | #. [App.Creator 2] - Creator 2 73 | #. defaultMessage is: 74 | #. Creator 75 | #: ./test/messages/src/containers/NotFound/messages.json 76 | #. [NotFound.Creator] - Creator 77 | #. defaultMessage is: 78 | #. Creator 79 | msgid "Creator" 80 | msgstr "" 81 | 82 | #: ./test/messages/src/containers/App/App.json 83 | #. [App.errorButton] - Click error Button 84 | #. defaultMessage is: 85 | #. Go to MCS website 86 | #: ./test/messages/src/containers/NotFound/messages.json 87 | #. [NotFound.errorButton] - Click error Button 88 | #. defaultMessage is: 89 | #. Go to MCS website 90 | msgid "Go to MCS website" 91 | msgstr "" 92 | 93 | #: ./test/messages/src/containers/App/App.json 94 | #. [App.errorMessage] - The error message when api response as 404 not found 95 | #. defaultMessage is: 96 | #. The device is now private or deleted. 97 | msgid "The device is now private or deleted." 98 | msgstr "" 99 | 100 | `; 101 | 102 | exports[`should return messages object with messageContext 1`] = ` 103 | msgid "" 104 | msgstr "" 105 | "POT-Creation-Date: 2017-02-01T11:23:12.000Z\\n" 106 | "Content-Type: text/plain; charset=UTF-8\\n" 107 | "Content-Transfer-Encoding: 8bit\\n" 108 | "MIME-Version: 1.0\\n" 109 | "X-Generator: react-intl-po\\n" 110 | 111 | 112 | #: ./test/messages/src/containers/App/App.json 113 | #. [App.Creator 1] - Creator 1 114 | #. defaultMessage is: 115 | #. Creator 116 | msgctxt "App.Creator 1" 117 | msgid "Creator" 118 | msgstr "" 119 | 120 | #: ./test/messages/src/containers/App/App.json 121 | #. [App.Creator 2] - Creator 2 122 | #. defaultMessage is: 123 | #. Creator 124 | msgctxt "App.Creator 2" 125 | msgid "Creator" 126 | msgstr "" 127 | 128 | #: ./test/messages/src/containers/NotFound/messages.json 129 | #. [NotFound.Creator] - Creator 130 | #. defaultMessage is: 131 | #. Creator 132 | msgctxt "NotFound.Creator" 133 | msgid "Creator" 134 | msgstr "" 135 | 136 | #: ./test/messages/src/containers/App/App.json 137 | #. [App.errorButton] - Click error Button 138 | #. defaultMessage is: 139 | #. Go to MCS website 140 | msgctxt "App.errorButton" 141 | msgid "Go to MCS website" 142 | msgstr "" 143 | 144 | #: ./test/messages/src/containers/NotFound/messages.json 145 | #. [NotFound.errorButton] - Click error Button 146 | #. defaultMessage is: 147 | #. Go to MCS website 148 | msgctxt "NotFound.errorButton" 149 | msgid "Go to MCS website" 150 | msgstr "" 151 | 152 | #: ./test/messages/src/containers/App/App.json 153 | #. [App.errorMessage] - The error message when api response as 404 not found 154 | #. defaultMessage is: 155 | #. The device is now private or deleted. 156 | msgctxt "App.errorMessage" 157 | msgid "The device is now private or deleted." 158 | msgstr "" 159 | 160 | `; 161 | -------------------------------------------------------------------------------- /test/__snapshots__/filterPOAndWriteTranslateSync.test.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`should output correct filter merged file with id as messageContext 1`] = ` 4 | { 5 | "zh-CN": { 6 | "App.errorMessage": "", 7 | "App.errorButton": "", 8 | "NotFound.errorButton": "", 9 | "App.Creator 1": "建立者(簡中1)", 10 | "App.Creator 2": "建立者(簡中2)", 11 | "NotFound.Creator": "" 12 | }, 13 | "zh-TW": { 14 | "App.errorMessage": "", 15 | "App.errorButton": "", 16 | "NotFound.errorButton": "", 17 | "App.Creator 1": "建立者(繁中1)", 18 | "App.Creator 2": "建立者(繁中2)", 19 | "NotFound.Creator": "" 20 | } 21 | } 22 | `; 23 | 24 | exports[`should output correct filter merged file with id as messageKey 1`] = ` 25 | { 26 | "zh-CN": { 27 | "App.errorMessage": "", 28 | "App.errorButton": "", 29 | "App.Creator 1": "建立者(簡中1)", 30 | "App.Creator 2": "建立者(簡中2)", 31 | "NotFound.errorButton": "", 32 | "NotFound.Creator": "" 33 | }, 34 | "zh-TW": { 35 | "App.errorMessage": "", 36 | "App.errorButton": "", 37 | "App.Creator 1": "建立者(繁中1)", 38 | "App.Creator 2": "建立者(繁中2)", 39 | "NotFound.errorButton": "", 40 | "NotFound.Creator": "" 41 | } 42 | } 43 | `; 44 | 45 | exports[`should output json file with prefix char if indentation char is used 1`] = ` 46 | { 47 | "zh-CN": { 48 | "App.errorMessage": "", 49 | "App.errorButton": "", 50 | "NotFound.errorButton": "", 51 | "App.Creator 1": "建立者(簡中)", 52 | "App.Creator 2": "建立者(簡中)", 53 | "NotFound.Creator": "建立者(簡中)" 54 | }, 55 | "zh-TW": { 56 | "App.errorMessage": "", 57 | "App.errorButton": "", 58 | "NotFound.errorButton": "", 59 | "App.Creator 1": "建立者", 60 | "App.Creator 2": "建立者", 61 | "NotFound.Creator": "建立者" 62 | } 63 | } 64 | `; 65 | 66 | exports[`should output json file with spaces if indentation number is used 1`] = ` 67 | { 68 | "zh-CN": { 69 | "App.errorMessage": "", 70 | "App.errorButton": "", 71 | "NotFound.errorButton": "", 72 | "App.Creator 1": "建立者(簡中)", 73 | "App.Creator 2": "建立者(簡中)", 74 | "NotFound.Creator": "建立者(簡中)" 75 | }, 76 | "zh-TW": { 77 | "App.errorMessage": "", 78 | "App.errorButton": "", 79 | "NotFound.errorButton": "", 80 | "App.Creator 1": "建立者", 81 | "App.Creator 2": "建立者", 82 | "NotFound.Creator": "建立者" 83 | } 84 | } 85 | `; 86 | 87 | exports[`should output one file per locale if a *directory* is set 1`] = ` 88 | { 89 | "App.errorMessage": "", 90 | "App.errorButton": "", 91 | "NotFound.errorButton": "", 92 | "App.Creator 1": "建立者(簡中)", 93 | "App.Creator 2": "建立者(簡中)", 94 | "NotFound.Creator": "建立者(簡中)" 95 | } 96 | `; 97 | 98 | exports[`should output one file per locale if a *directory* is set 2`] = ` 99 | { 100 | "App.errorMessage": "", 101 | "App.errorButton": "", 102 | "NotFound.errorButton": "", 103 | "App.Creator 1": "建立者", 104 | "App.Creator 2": "建立者", 105 | "NotFound.Creator": "建立者" 106 | } 107 | `; 108 | 109 | exports[`should output one file per locale if a *directory* is set, using custom lang mapper regex 1`] = ` 110 | { 111 | "App.errorMessage": "", 112 | "App.errorButton": "", 113 | "NotFound.errorButton": "", 114 | "App.Creator 1": "", 115 | "App.Creator 2": "", 116 | "NotFound.Creator": "" 117 | } 118 | `; 119 | 120 | exports[`should output one file per locale if a *directory* is set, using custom lang mapper regex 2`] = ` 121 | { 122 | "App.errorMessage": "", 123 | "App.errorButton": "", 124 | "NotFound.errorButton": "", 125 | "App.Creator 1": "", 126 | "App.Creator 2": "", 127 | "NotFound.Creator": "" 128 | } 129 | `; 130 | 131 | exports[`should output one merged file if a *json file* is set 1`] = ` 132 | { 133 | "zh-CN": { 134 | "App.errorMessage": "", 135 | "App.errorButton": "", 136 | "NotFound.errorButton": "", 137 | "App.Creator 1": "建立者(簡中)", 138 | "App.Creator 2": "建立者(簡中)", 139 | "NotFound.Creator": "建立者(簡中)" 140 | }, 141 | "zh-TW": { 142 | "App.errorMessage": "", 143 | "App.errorButton": "", 144 | "NotFound.errorButton": "", 145 | "App.Creator 1": "建立者", 146 | "App.Creator 2": "建立者", 147 | "NotFound.Creator": "建立者" 148 | } 149 | } 150 | `; 151 | 152 | exports[`should output sorted file if sortById is used 1`] = ` 153 | { 154 | "zh-CN": { 155 | "App.Creator 1": "建立者(簡中)", 156 | "App.Creator 2": "建立者(簡中)", 157 | "App.errorButton": "", 158 | "App.errorMessage": "", 159 | "NotFound.Creator": "建立者(簡中)", 160 | "NotFound.errorButton": "" 161 | }, 162 | "zh-TW": { 163 | "App.Creator 1": "建立者", 164 | "App.Creator 2": "建立者", 165 | "App.errorButton": "", 166 | "App.errorMessage": "", 167 | "NotFound.Creator": "建立者", 168 | "NotFound.errorButton": "" 169 | } 170 | } 171 | `; 172 | -------------------------------------------------------------------------------- /test/__snapshots__/potFormater.test.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`should return pot formatted string 1`] = ` 4 | #: ./messages/src/containers/App/App.json 5 | #. [App.errorButton] - Click error Button 6 | #. defaultMessage is: 7 | #. Go to MCS website 8 | #: ./messages/src/containers/NotFound/messages.json 9 | #. [NotFound.errorButton] - Click error Button 10 | #. defaultMessage is: 11 | #. Go to MCS website 12 | msgid "Go to MCS website" 13 | msgstr "" 14 | 15 | `; 16 | 17 | exports[`should return pot formatted string, with double quotes escaped 1`] = ` 18 | #: ./messages/src/containers/NotFound/messages.json 19 | #. [NotFound.errorButton] - My description 20 | #. is 21 | #. quite 22 | #. long. 23 | #. defaultMessage is: 24 | #. This is "quoted" 25 | msgid "This is \\"quoted\\"" 26 | msgstr "" 27 | 28 | `; 29 | 30 | exports[`should return pot formatted string, with message context 1`] = ` 31 | #: ./messages/src/containers/App/App.json 32 | #. [App.errorButton] - Click error Button 33 | #. defaultMessage is: 34 | #. Go to MCS website 35 | msgctxt "App.errorButton" 36 | msgid "Go to MCS website" 37 | msgstr "" 38 | 39 | #: ./messages/src/containers/NotFound/messages.json 40 | #. [NotFound.errorButton] - Click error Button 41 | #. defaultMessage is: 42 | #. Go to MCS website 43 | msgctxt "NotFound.errorButton" 44 | msgid "Go to MCS website" 45 | msgstr "" 46 | 47 | `; 48 | 49 | exports[`should return pot formatted string, with multi line values 1`] = ` 50 | #: ./messages/src/containers/NotFound/messages.json 51 | #. [NotFound.errorButton] - My description 52 | #. is 53 | #. quite 54 | #. long. 55 | #. defaultMessage is: 56 | #. This is 57 | #. multiline 58 | msgid "NotFound.errorButton" 59 | msgstr "" 60 | 61 | `; 62 | 63 | exports[`should return pot formatted string, with null or undefined description 1`] = ` 64 | #: ./messages/src/containers/NotFound/messages.json 65 | #. [NotFound.errorButton] 66 | #. defaultMessage is: 67 | #. Go to MCS website 68 | msgid "Go to MCS website" 69 | msgstr "" 70 | 71 | `; 72 | -------------------------------------------------------------------------------- /test/__snapshots__/potHeader.test.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`should return pot header, with a list of comments 1`] = ` 4 | # A 5 | # B 6 | # C 7 | msgid "" 8 | msgstr "" 9 | "MIME-Version: 1.0\\n" 10 | "X-Generator: react-intl-po\\n" 11 | 12 | 13 | 14 | `; 15 | 16 | exports[`should return pot header, with a single comment 1`] = ` 17 | # This is a single line comment 18 | msgid "" 19 | msgstr "" 20 | "MIME-Version: 1.0\\n" 21 | "X-Generator: react-intl-po\\n" 22 | 23 | 24 | 25 | `; 26 | 27 | exports[`should return pot header, with a single comment, with CR in it 1`] = ` 28 | # This is a multi-line 29 | # comment 30 | # 31 | msgid "" 32 | msgstr "" 33 | "MIME-Version: 1.0\\n" 34 | "X-Generator: react-intl-po\\n" 35 | 36 | 37 | 38 | `; 39 | 40 | exports[`should return pot header, with all options 1`] = ` 41 | # This is a single line comment 42 | msgid "" 43 | msgstr "" 44 | "Project-Id-Version: FUBAR\\n" 45 | "POT-Creation-Date: 1995-12-17T03:24:00.000Z\\n" 46 | "Content-Type: text/plain; charset=UTF-8\\n" 47 | "Content-Transfer-Encoding: 8bit\\n" 48 | "MIME-Version: 1.0\\n" 49 | "X-Generator: react-intl-po\\n" 50 | 51 | 52 | 53 | `; 54 | 55 | exports[`should return pot header, without any parameter 1`] = ` 56 | msgid "" 57 | msgstr "" 58 | "MIME-Version: 1.0\\n" 59 | "X-Generator: react-intl-po\\n" 60 | 61 | 62 | 63 | `; 64 | 65 | exports[`should return pot header, without empty options 1`] = ` 66 | msgid "" 67 | msgstr "" 68 | "MIME-Version: 1.0\\n" 69 | "X-Generator: react-intl-po\\n" 70 | 71 | 72 | 73 | `; 74 | -------------------------------------------------------------------------------- /test/__snapshots__/readAllMessageAsObjectSync.test.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`should return messages object with default messageKey 1`] = ` 4 | { 5 | "The device is now private or deleted.": { 6 | "": [ 7 | { 8 | "id": "App.errorMessage", 9 | "description": "The error message when api response as 404 not found", 10 | "defaultMessage": "The device is now private or deleted.", 11 | "filename": "./test/messages/src/containers/App/App.json" 12 | } 13 | ] 14 | }, 15 | "Go to MCS website": { 16 | "": [ 17 | { 18 | "id": "App.errorButton", 19 | "description": "Click error Button", 20 | "defaultMessage": "Go to MCS website", 21 | "filename": "./test/messages/src/containers/App/App.json" 22 | }, 23 | { 24 | "id": "NotFound.errorButton", 25 | "description": "Click error Button", 26 | "defaultMessage": "Go to MCS website", 27 | "filename": "./test/messages/src/containers/NotFound/messages.json" 28 | } 29 | ] 30 | }, 31 | "Creator": { 32 | "": [ 33 | { 34 | "id": "App.Creator 1", 35 | "description": "Creator 1", 36 | "defaultMessage": "Creator", 37 | "filename": "./test/messages/src/containers/App/App.json" 38 | }, 39 | { 40 | "id": "App.Creator 2", 41 | "description": "Creator 2", 42 | "defaultMessage": "Creator", 43 | "filename": "./test/messages/src/containers/App/App.json" 44 | }, 45 | { 46 | "id": "NotFound.Creator", 47 | "description": "Creator", 48 | "defaultMessage": "Creator", 49 | "filename": "./test/messages/src/containers/NotFound/messages.json" 50 | } 51 | ] 52 | } 53 | } 54 | `; 55 | 56 | exports[`should return messages object with description as key 1`] = ` 57 | { 58 | "The error message when api response as 404 not found": { 59 | "": [ 60 | { 61 | "id": "App.errorMessage", 62 | "description": "The error message when api response as 404 not found", 63 | "defaultMessage": "The device is now private or deleted.", 64 | "filename": "./test/messages/src/containers/App/App.json" 65 | } 66 | ] 67 | }, 68 | "Click error Button": { 69 | "": [ 70 | { 71 | "id": "App.errorButton", 72 | "description": "Click error Button", 73 | "defaultMessage": "Go to MCS website", 74 | "filename": "./test/messages/src/containers/App/App.json" 75 | } 76 | ] 77 | }, 78 | "Creator 1": { 79 | "": [ 80 | { 81 | "id": "App.Creator 1", 82 | "description": "Creator 1", 83 | "defaultMessage": "Creator", 84 | "filename": "./test/messages/src/containers/App/App.json" 85 | } 86 | ] 87 | }, 88 | "Creator 2": { 89 | "": [ 90 | { 91 | "id": "App.Creator 2", 92 | "description": "Creator 2", 93 | "defaultMessage": "Creator", 94 | "filename": "./test/messages/src/containers/App/App.json" 95 | } 96 | ] 97 | } 98 | } 99 | `; 100 | 101 | exports[`should return messages object with id as context 1`] = ` 102 | { 103 | "The device is now private or deleted.": { 104 | "App.errorMessage": [ 105 | { 106 | "id": "App.errorMessage", 107 | "description": "The error message when api response as 404 not found", 108 | "defaultMessage": "The device is now private or deleted.", 109 | "filename": "./test/messages/src/containers/App/App.json" 110 | } 111 | ] 112 | }, 113 | "Go to MCS website": { 114 | "App.errorButton": [ 115 | { 116 | "id": "App.errorButton", 117 | "description": "Click error Button", 118 | "defaultMessage": "Go to MCS website", 119 | "filename": "./test/messages/src/containers/App/App.json" 120 | } 121 | ], 122 | "NotFound.errorButton": [ 123 | { 124 | "id": "NotFound.errorButton", 125 | "description": "Click error Button", 126 | "defaultMessage": "Go to MCS website", 127 | "filename": "./test/messages/src/containers/NotFound/messages.json" 128 | } 129 | ] 130 | }, 131 | "Creator": { 132 | "App.Creator 1": [ 133 | { 134 | "id": "App.Creator 1", 135 | "description": "Creator 1", 136 | "defaultMessage": "Creator", 137 | "filename": "./test/messages/src/containers/App/App.json" 138 | } 139 | ], 140 | "App.Creator 2": [ 141 | { 142 | "id": "App.Creator 2", 143 | "description": "Creator 2", 144 | "defaultMessage": "Creator", 145 | "filename": "./test/messages/src/containers/App/App.json" 146 | } 147 | ], 148 | "NotFound.Creator": [ 149 | { 150 | "id": "NotFound.Creator", 151 | "description": "Creator", 152 | "defaultMessage": "Creator", 153 | "filename": "./test/messages/src/containers/NotFound/messages.json" 154 | } 155 | ] 156 | } 157 | } 158 | `; 159 | 160 | exports[`should return messages object with id as key 1`] = ` 161 | { 162 | "App.errorMessage": { 163 | "": [ 164 | { 165 | "id": "App.errorMessage", 166 | "description": "The error message when api response as 404 not found", 167 | "defaultMessage": "The device is now private or deleted.", 168 | "filename": "./test/messages/src/containers/App/App.json" 169 | } 170 | ] 171 | }, 172 | "App.errorButton": { 173 | "": [ 174 | { 175 | "id": "App.errorButton", 176 | "description": "Click error Button", 177 | "defaultMessage": "Go to MCS website", 178 | "filename": "./test/messages/src/containers/App/App.json" 179 | } 180 | ] 181 | }, 182 | "App.Creator 1": { 183 | "": [ 184 | { 185 | "id": "App.Creator 1", 186 | "description": "Creator 1", 187 | "defaultMessage": "Creator", 188 | "filename": "./test/messages/src/containers/App/App.json" 189 | } 190 | ] 191 | }, 192 | "App.Creator 2": { 193 | "": [ 194 | { 195 | "id": "App.Creator 2", 196 | "description": "Creator 2", 197 | "defaultMessage": "Creator", 198 | "filename": "./test/messages/src/containers/App/App.json" 199 | } 200 | ] 201 | }, 202 | "NotFound.errorButton": { 203 | "": [ 204 | { 205 | "id": "NotFound.errorButton", 206 | "description": "Click error Button", 207 | "defaultMessage": "Go to MCS website", 208 | "filename": "./test/messages/src/containers/NotFound/messages.json" 209 | } 210 | ] 211 | }, 212 | "NotFound.Creator": { 213 | "": [ 214 | { 215 | "id": "NotFound.Creator", 216 | "description": "Creator", 217 | "defaultMessage": "Creator", 218 | "filename": "./test/messages/src/containers/NotFound/messages.json" 219 | } 220 | ] 221 | } 222 | } 223 | `; 224 | -------------------------------------------------------------------------------- /test/__snapshots__/readAllPOAsObjectSync.test.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`should return po object with custom localeMapper 1`] = ` 4 | { 5 | "zh-CN": { 6 | "( default )": "(默认值)" 7 | }, 8 | "zh-TW": { 9 | "( default )": "(默認值)" 10 | } 11 | } 12 | `; 13 | 14 | exports[`should return po object with default localeMapper 1`] = ` 15 | { 16 | "zh-CN": { 17 | "Creator": "建立者(簡中)", 18 | "Version": "版本(簡中)" 19 | }, 20 | "zh-TW": { 21 | "Creator": "建立者", 22 | "Version": "版本" 23 | } 24 | } 25 | `; 26 | -------------------------------------------------------------------------------- /test/alternate_po/zh_CN/LC_MESSAGES/messages.po: -------------------------------------------------------------------------------- 1 | msgid "" 2 | msgstr "" 3 | "POT-Creation-Date: 2017-02-01T11:23:12.000Z\n" 4 | "Content-Type: text/plain; charset=UTF-8\n" 5 | "Content-Transfer-Encoding: 8bit\n" 6 | "MIME-Version: 1.0\n" 7 | "X-Generator: react-intl-po\n" 8 | 9 | 10 | #: ./test/messages/src/containers/App/App.json 11 | #. [App.Creator 1] - Creator 1 12 | msgctxt "App.Creator 1" 13 | msgid "Creator" 14 | msgstr "建立者(簡中1)" 15 | 16 | #: ./test/messages/src/containers/App/App.json 17 | #. [App.Creator 2] - Creator 2 18 | msgctxt "App.Creator 2" 19 | msgid "Creator" 20 | msgstr "建立者(簡中2)" 21 | -------------------------------------------------------------------------------- /test/alternate_po/zh_TW/LC_MESSAGES/messages.po: -------------------------------------------------------------------------------- 1 | msgid "" 2 | msgstr "" 3 | "POT-Creation-Date: 2017-02-01T11:23:12.000Z\n" 4 | "Content-Type: text/plain; charset=UTF-8\n" 5 | "Content-Transfer-Encoding: 8bit\n" 6 | "MIME-Version: 1.0\n" 7 | "X-Generator: react-intl-po\n" 8 | 9 | 10 | #: ./test/messages/src/containers/App/App.json 11 | #. [App.Creator 1] - Creator 1 12 | msgctxt "App.Creator 1" 13 | msgid "Creator" 14 | msgstr "建立者(繁中1)" 15 | 16 | #: ./test/messages/src/containers/App/App.json 17 | #. [App.Creator 2] - Creator 2 18 | msgctxt "App.Creator 2" 19 | msgid "Creator" 20 | msgstr "建立者(繁中2)" 21 | -------------------------------------------------------------------------------- /test/extractAndWritePOTFromMessagesSync.test.js: -------------------------------------------------------------------------------- 1 | import fs from 'fs'; 2 | import extractAndWritePOTFromMessagesSync from '../src/extractAndWritePOTFromMessagesSync'; 3 | 4 | it('should return a function', () => { 5 | expect(typeof extractAndWritePOTFromMessagesSync).toBe('function'); 6 | }); 7 | 8 | it('should return messages object with default mapper', () => { 9 | const output = './test/temp/extract-defaultMessage.pot'; 10 | const headerOptions = { 11 | potCreationDate: new Date(Date.UTC(2017, 1, 1, 11, 23, 12)), 12 | }; 13 | 14 | extractAndWritePOTFromMessagesSync('./test/messages/**/*.json', { 15 | output, 16 | headerOptions, 17 | }); 18 | expect(fs.readFileSync(output, 'utf8')).toMatchSnapshot(); 19 | }); 20 | 21 | it('should return messages object with custom message key mapper', () => { 22 | const output = './test/temp/extract-id.pot'; 23 | const headerOptions = { 24 | potCreationDate: new Date(Date.UTC(2017, 1, 1, 11, 23, 12)), 25 | }; 26 | 27 | extractAndWritePOTFromMessagesSync('./test/messages/**/*.json', { 28 | messageKey: 'id', 29 | output, 30 | headerOptions, 31 | }); 32 | expect(fs.readFileSync(output, 'utf8')).toMatchSnapshot(); 33 | }); 34 | 35 | it('should return messages object with messageContext', () => { 36 | const output = './test/temp/extract-messageContext.pot'; 37 | const headerOptions = { 38 | potCreationDate: new Date(Date.UTC(2017, 1, 1, 11, 23, 12)), 39 | }; 40 | 41 | extractAndWritePOTFromMessagesSync('./test/messages/**/*.json', { 42 | output, 43 | headerOptions, 44 | messageContext: 'id', 45 | }); 46 | expect(fs.readFileSync(output, 'utf8')).toMatchSnapshot(); 47 | }); 48 | -------------------------------------------------------------------------------- /test/filterPOAndWriteTranslateSync.test.js: -------------------------------------------------------------------------------- 1 | import fs from 'fs'; 2 | import filterPOAndWriteTranslateSync from '../src/filterPOAndWriteTranslateSync'; 3 | 4 | it('should return a function', () => { 5 | expect(typeof filterPOAndWriteTranslateSync).toBe('function'); 6 | }); 7 | 8 | it('should output one merged file if a *json file* is set', () => { 9 | const messagesPattern = './test/messages/**/*.json'; 10 | const output = './test/temp/translations-defaultMessage.json'; 11 | 12 | filterPOAndWriteTranslateSync('./test/po/mcs-public.*.po', { 13 | messagesPattern, 14 | output, 15 | }); 16 | expect(JSON.parse(fs.readFileSync(output, 'utf8'))).toMatchSnapshot(); 17 | }); 18 | 19 | it('should output one file per locale if a *directory* is set', () => { 20 | const messagesPattern = './test/messages/**/*.json'; 21 | const output = './test/temp/translations'; 22 | 23 | filterPOAndWriteTranslateSync('./test/po/mcs-public.*.po', { 24 | messagesPattern, 25 | output, 26 | }); 27 | expect( 28 | JSON.parse(fs.readFileSync(`${output}/zh-CN.json`, 'utf8')), 29 | ).toMatchSnapshot(); 30 | expect( 31 | JSON.parse(fs.readFileSync(`${output}/zh-TW.json`, 'utf8')), 32 | ).toMatchSnapshot(); 33 | }); 34 | 35 | it('should output one file per locale if a *directory* is set, using custom lang mapper regex', () => { 36 | const messagesPattern = './test/messages/**/*.json'; 37 | const output = './test/temp/translations'; 38 | const langMapperPattern = /.*\/(\w{1,3}_\w{1,3})\/.*/; 39 | const langMapperPatternIndex = 1; 40 | 41 | filterPOAndWriteTranslateSync('./test/alternate_po/**/messages.po', { 42 | messagesPattern, 43 | langMapperPattern, 44 | langMapperPatternIndex, 45 | output, 46 | }); 47 | expect( 48 | JSON.parse(fs.readFileSync(`${output}/zh_CN.json`, 'utf8')), 49 | ).toMatchSnapshot(); 50 | expect( 51 | JSON.parse(fs.readFileSync(`${output}/zh_TW.json`, 'utf8')), 52 | ).toMatchSnapshot(); 53 | }); 54 | 55 | it('should output correct filter merged file with id as messageKey', () => { 56 | const messagesPattern = './test/messages/**/*.json'; 57 | const output = './test/temp/translations-id.json'; 58 | 59 | filterPOAndWriteTranslateSync('./test/po/mcs-id.*.po', { 60 | messageKey: 'id', 61 | messagesPattern, 62 | output, 63 | }); 64 | expect(JSON.parse(fs.readFileSync(output, 'utf8'))).toMatchSnapshot(); 65 | }); 66 | 67 | it('should output correct filter merged file with id as messageContext', () => { 68 | const messagesPattern = './test/messages/**/*.json'; 69 | const output = './test/temp/translations-ctxt.json'; 70 | 71 | filterPOAndWriteTranslateSync('./test/po/mcs-ctxt.*.po', { 72 | messageContext: 'id', 73 | messagesPattern, 74 | output, 75 | }); 76 | expect(JSON.parse(fs.readFileSync(output, 'utf8'))).toMatchSnapshot(); 77 | }); 78 | 79 | it('should output json file with spaces if indentation number is used', () => { 80 | const messagesPattern = './test/messages/**/*.json'; 81 | const output = './test/temp/translations-defaultMessage-indent4.json'; 82 | const indentation = 4; 83 | 84 | filterPOAndWriteTranslateSync('./test/po/mcs-public.*.po', { 85 | messagesPattern, 86 | output, 87 | indentation, 88 | }); 89 | expect(fs.readFileSync(output, 'utf8')).toMatchSnapshot(); 90 | }); 91 | 92 | it('should output json file with prefix char if indentation char is used', () => { 93 | const messagesPattern = './test/messages/**/*.json'; 94 | const output = './test/temp/translations-defaultMessage-indentchar.json'; 95 | const indentation = '\t\t'; 96 | 97 | filterPOAndWriteTranslateSync('./test/po/mcs-public.*.po', { 98 | messagesPattern, 99 | output, 100 | indentation, 101 | }); 102 | expect(fs.readFileSync(output, 'utf8')).toMatchSnapshot(); 103 | }); 104 | 105 | it('should output sorted file if sortById is used', () => { 106 | const messagesPattern = './test/messages/**/*.json'; 107 | const output = './test/temp/translations-defaultMessage-indentchar.json'; 108 | const sortById = true; 109 | 110 | filterPOAndWriteTranslateSync('./test/po/mcs-public.*.po', { 111 | messagesPattern, 112 | output, 113 | sortById, 114 | }); 115 | expect(JSON.parse(fs.readFileSync(output, 'utf8'))).toMatchSnapshot(); 116 | }); 117 | -------------------------------------------------------------------------------- /test/messages/src/containers/App/App.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": "App.errorMessage", 4 | "description": "The error message when api response as 404 not found", 5 | "defaultMessage": "The device is now private or deleted." 6 | }, 7 | { 8 | "id": "App.errorButton", 9 | "description": "Click error Button", 10 | "defaultMessage": "Go to MCS website" 11 | }, 12 | { 13 | "id": "App.Creator 1", 14 | "description": "Creator 1", 15 | "defaultMessage": "Creator" 16 | }, 17 | { 18 | "id": "App.Creator 2", 19 | "description": "Creator 2", 20 | "defaultMessage": "Creator" 21 | } 22 | ] 23 | -------------------------------------------------------------------------------- /test/messages/src/containers/NotFound/messages.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": "NotFound.errorButton", 4 | "description": "Click error Button", 5 | "defaultMessage": "Go to MCS website" 6 | }, 7 | { 8 | "id": "NotFound.Creator", 9 | "description": "Creator", 10 | "defaultMessage": "Creator" 11 | } 12 | ] 13 | -------------------------------------------------------------------------------- /test/po/mcs-ctxt.zh-CN.po: -------------------------------------------------------------------------------- 1 | msgid "" 2 | msgstr "" 3 | "POT-Creation-Date: 2017-02-01T11:23:12.000Z\n" 4 | "Content-Type: text/plain; charset=UTF-8\n" 5 | "Content-Transfer-Encoding: 8bit\n" 6 | "MIME-Version: 1.0\n" 7 | "X-Generator: react-intl-po\n" 8 | 9 | 10 | #: ./test/messages/src/containers/App/App.json 11 | #. [App.Creator 1] - Creator 1 12 | msgctxt "App.Creator 1" 13 | msgid "Creator" 14 | msgstr "建立者(簡中1)" 15 | 16 | #: ./test/messages/src/containers/App/App.json 17 | #. [App.Creator 2] - Creator 2 18 | msgctxt "App.Creator 2" 19 | msgid "Creator" 20 | msgstr "建立者(簡中2)" 21 | -------------------------------------------------------------------------------- /test/po/mcs-ctxt.zh-TW.po: -------------------------------------------------------------------------------- 1 | msgid "" 2 | msgstr "" 3 | "POT-Creation-Date: 2017-02-01T11:23:12.000Z\n" 4 | "Content-Type: text/plain; charset=UTF-8\n" 5 | "Content-Transfer-Encoding: 8bit\n" 6 | "MIME-Version: 1.0\n" 7 | "X-Generator: react-intl-po\n" 8 | 9 | 10 | #: ./test/messages/src/containers/App/App.json 11 | #. [App.Creator 1] - Creator 1 12 | msgctxt "App.Creator 1" 13 | msgid "Creator" 14 | msgstr "建立者(繁中1)" 15 | 16 | #: ./test/messages/src/containers/App/App.json 17 | #. [App.Creator 2] - Creator 2 18 | msgctxt "App.Creator 2" 19 | msgid "Creator" 20 | msgstr "建立者(繁中2)" 21 | -------------------------------------------------------------------------------- /test/po/mcs-id.zh-CN.po: -------------------------------------------------------------------------------- 1 | msgid "" 2 | msgstr "" 3 | "POT-Creation-Date: 2017-02-01T11:23:12.000Z\n" 4 | "Content-Type: text/plain; charset=UTF-8\n" 5 | "Content-Transfer-Encoding: 8bit\n" 6 | "MIME-Version: 1.0\n" 7 | "X-Generator: react-intl-po\n" 8 | 9 | 10 | #: ./test/messages/src/containers/App/App.json 11 | #. [App.Creator 1] - Creator 1 12 | msgid "App.Creator 1" 13 | msgstr "建立者(簡中1)" 14 | 15 | #: ./test/messages/src/containers/App/App.json 16 | #. [App.Creator 2] - Creator 2 17 | msgid "App.Creator 2" 18 | msgstr "建立者(簡中2)" 19 | -------------------------------------------------------------------------------- /test/po/mcs-id.zh-TW.po: -------------------------------------------------------------------------------- 1 | msgid "" 2 | msgstr "" 3 | "POT-Creation-Date: 2017-02-01T11:23:12.000Z\n" 4 | "Content-Type: text/plain; charset=UTF-8\n" 5 | "Content-Transfer-Encoding: 8bit\n" 6 | "MIME-Version: 1.0\n" 7 | "X-Generator: react-intl-po\n" 8 | 9 | 10 | #: ./test/messages/src/containers/App/App.json 11 | #. [App.Creator 1] - Creator 1 12 | msgid "App.Creator 1" 13 | msgstr "建立者(繁中1)" 14 | 15 | #: ./test/messages/src/containers/App/App.json 16 | #. [App.Creator 2] - Creator 2 17 | msgid "App.Creator 2" 18 | msgstr "建立者(繁中2)" 19 | -------------------------------------------------------------------------------- /test/po/mcs-public.zh-CN.po: -------------------------------------------------------------------------------- 1 | msgid "" 2 | msgstr "" 3 | "Project-Id-Version: mcs-public\n" 4 | "POT-Creation-Date: \n" 5 | "PO-Revision-Date: \n" 6 | "Last-Translator: \n" 7 | "Language-Team: \n" 8 | "Language: zh_CN\n" 9 | "MIME-Version: 1.0\n" 10 | "Content-Type: text/plain; charset=UTF-8\n" 11 | "Content-Transfer-Encoding: 8bit\n" 12 | "X-Generator: Poedit 1.7.5\n" 13 | "Plural-Forms: nplurals=1; plural=0;\n" 14 | 15 | #. [Device.creator] - Creator 16 | #: _translations/src/containers/Device/messages.json 17 | msgid "Creator" 18 | msgstr "建立者(簡中)" 19 | 20 | #. [Device.version] - Version 21 | #: _translations/src/containers/Device/messages.json 22 | msgid "Version" 23 | msgstr "版本(簡中)" 24 | -------------------------------------------------------------------------------- /test/po/mcs-public.zh-TW.po: -------------------------------------------------------------------------------- 1 | msgid "" 2 | msgstr "" 3 | "Project-Id-Version: mcs-public\n" 4 | "POT-Creation-Date: \n" 5 | "PO-Revision-Date: \n" 6 | "Last-Translator: \n" 7 | "Language-Team: \n" 8 | "Language: zh_TW\n" 9 | "MIME-Version: 1.0\n" 10 | "Content-Type: text/plain; charset=UTF-8\n" 11 | "Content-Transfer-Encoding: 8bit\n" 12 | "X-Generator: Poedit 1.7.5\n" 13 | "Plural-Forms: nplurals=1; plural=0;\n" 14 | 15 | #. [Device.creator] - Creator 16 | #: _translations/src/containers/Device/messages.json 17 | msgid "Creator" 18 | msgstr "建立者" 19 | 20 | #. [Device.version] - Version 21 | #: _translations/src/containers/Device/messages.json 22 | msgid "Version" 23 | msgstr "版本" 24 | -------------------------------------------------------------------------------- /test/po/zh-CN_project.po: -------------------------------------------------------------------------------- 1 | msgid "" 2 | msgstr "" 3 | "Project-Id-Version: \n" 4 | "POT-Creation-Date: \n" 5 | "PO-Revision-Date: \n" 6 | "Last-Translator: \n" 7 | "Language-Team: \n" 8 | "Language: zh_CN\n" 9 | "MIME-Version: 1.0\n" 10 | "Content-Type: text/plain; charset=UTF-8\n" 11 | "Content-Transfer-Encoding: 8bit\n" 12 | "X-Generator: Poedit 1.7.5\n" 13 | "Plural-Forms: nplurals=2; plural=0;\n" 14 | 15 | #: mydevices/dialogs/trigger.html:54 mydevices/dialogs/trigger.html:67 16 | msgid "( default )" 17 | msgstr "(默认值)" 18 | -------------------------------------------------------------------------------- /test/po/zh-TW_project.po: -------------------------------------------------------------------------------- 1 | msgid "" 2 | msgstr "" 3 | "Project-Id-Version: \n" 4 | "POT-Creation-Date: \n" 5 | "PO-Revision-Date: \n" 6 | "Last-Translator: \n" 7 | "Language-Team: \n" 8 | "Language: zh_TW\n" 9 | "MIME-Version: 1.0\n" 10 | "Content-Type: text/plain; charset=UTF-8\n" 11 | "Content-Transfer-Encoding: 8bit\n" 12 | "X-Generator: Poedit 1.7.5\n" 13 | "Plural-Forms: nplurals=2; plural=0;\n" 14 | 15 | #: mydevices/dialogs/trigger.html:54 mydevices/dialogs/trigger.html:67 16 | msgid "( default )" 17 | msgstr "(默認值)" 18 | -------------------------------------------------------------------------------- /test/potFormater.test.js: -------------------------------------------------------------------------------- 1 | import potFormater from '../src/potFormater'; 2 | 3 | it('should return a function', () => { 4 | expect(typeof potFormater).toBe('function'); 5 | }); 6 | 7 | it('should return pot formatted string', () => { 8 | expect( 9 | potFormater({ 10 | 'Go to MCS website': { 11 | '': [ 12 | { 13 | id: 'App.errorButton', 14 | description: 'Click error Button', 15 | defaultMessage: 'Go to MCS website', 16 | filename: './messages/src/containers/App/App.json', 17 | }, 18 | { 19 | id: 'NotFound.errorButton', 20 | description: 'Click error Button', 21 | defaultMessage: 'Go to MCS website', 22 | filename: './messages/src/containers/NotFound/messages.json', 23 | }, 24 | ], 25 | }, 26 | }), 27 | ).toMatchSnapshot(); 28 | }); 29 | 30 | it('should return pot formatted string, with null or undefined description', () => { 31 | expect( 32 | potFormater({ 33 | 'Go to MCS website': { 34 | '': [ 35 | { 36 | id: 'NotFound.errorButton', 37 | defaultMessage: 'Go to MCS website', 38 | filename: './messages/src/containers/NotFound/messages.json', 39 | }, 40 | ], 41 | }, 42 | }), 43 | ).toMatchSnapshot(); 44 | }); 45 | 46 | it('should return pot formatted string, with multi line values', () => { 47 | expect( 48 | potFormater({ 49 | 'NotFound.errorButton': { 50 | '': [ 51 | { 52 | id: 'NotFound.errorButton', 53 | description: 'My description\nis\nquite\nlong.', 54 | defaultMessage: 'This is\nmultiline', 55 | filename: './messages/src/containers/NotFound/messages.json', 56 | }, 57 | ], 58 | }, 59 | }), 60 | ).toMatchSnapshot(); 61 | }); 62 | 63 | it('should return pot formatted string, with double quotes escaped', () => { 64 | expect( 65 | potFormater({ 66 | 'This is "quoted"': { 67 | '': [ 68 | { 69 | id: 'NotFound.errorButton', 70 | description: 'My description\nis\nquite\nlong.', 71 | defaultMessage: 'This is "quoted"', 72 | filename: './messages/src/containers/NotFound/messages.json', 73 | }, 74 | ], 75 | }, 76 | }), 77 | ).toMatchSnapshot(); 78 | }); 79 | 80 | it('should return pot formatted string, with message context', () => { 81 | expect( 82 | potFormater({ 83 | 'Go to MCS website': { 84 | 'App.errorButton': [ 85 | { 86 | id: 'App.errorButton', 87 | description: 'Click error Button', 88 | defaultMessage: 'Go to MCS website', 89 | filename: './messages/src/containers/App/App.json', 90 | }, 91 | ], 92 | 'NotFound.errorButton': [ 93 | { 94 | id: 'NotFound.errorButton', 95 | description: 'Click error Button', 96 | defaultMessage: 'Go to MCS website', 97 | filename: './messages/src/containers/NotFound/messages.json', 98 | }, 99 | ], 100 | }, 101 | }), 102 | ).toMatchSnapshot(); 103 | }); 104 | -------------------------------------------------------------------------------- /test/potHeader.test.js: -------------------------------------------------------------------------------- 1 | import potHeader from '../src/potHeader'; 2 | 3 | it('should return a function', () => { 4 | expect(typeof potHeader).toBe('function'); 5 | }); 6 | 7 | it('should return pot header, without any parameter', () => { 8 | expect(potHeader()).toMatchSnapshot(); 9 | }); 10 | 11 | it('should return pot header, without empty options', () => { 12 | expect(potHeader({})).toMatchSnapshot(); 13 | }); 14 | 15 | it('should return pot header, with a single comment', () => { 16 | expect( 17 | potHeader({ 18 | comments: 'This is a single line comment', 19 | }), 20 | ).toMatchSnapshot(); 21 | }); 22 | 23 | it('should return pot header, with a single comment, with CR in it', () => { 24 | expect( 25 | potHeader({ 26 | comments: 'This is a multi-line\ncomment\n', 27 | }), 28 | ).toMatchSnapshot(); 29 | }); 30 | 31 | it('should return pot header, with a list of comments', () => { 32 | expect( 33 | potHeader({ 34 | comments: ['A', 'B', 'C'], 35 | }), 36 | ).toMatchSnapshot(); 37 | }); 38 | 39 | it('should return pot header, with all options', () => { 40 | expect( 41 | potHeader({ 42 | comments: 'This is a single line comment', 43 | projectIdVersion: 'FUBAR', 44 | potCreationDate: new Date(Date.UTC(1995, 11, 17, 3, 24, 0)), 45 | charset: 'UTF-8', 46 | encoding: '8bit', 47 | }), 48 | ).toMatchSnapshot(); 49 | }); 50 | -------------------------------------------------------------------------------- /test/readAllMessageAsObjectSync.test.js: -------------------------------------------------------------------------------- 1 | import readAllMessageAsObjectSync from '../src/readAllMessageAsObjectSync'; 2 | 3 | it('should return a function', () => { 4 | expect(typeof readAllMessageAsObjectSync).toBe('function'); 5 | }); 6 | 7 | it('should return messages object with default messageKey', () => { 8 | expect( 9 | readAllMessageAsObjectSync('./test/messages/**/*.json'), 10 | ).toMatchSnapshot(); 11 | }); 12 | 13 | it('should return messages object with description as key', () => { 14 | expect( 15 | readAllMessageAsObjectSync('./test/messages/**/App.json', 'description'), 16 | ).toMatchSnapshot(); 17 | }); 18 | 19 | it('should return messages object with id as key', () => { 20 | expect( 21 | readAllMessageAsObjectSync('./test/messages/**/*.json', 'id'), 22 | ).toMatchSnapshot(); 23 | }); 24 | 25 | it('should return messages object with id as context', () => { 26 | expect( 27 | readAllMessageAsObjectSync('./test/messages/**/*.json', undefined, 'id'), 28 | ).toMatchSnapshot(); 29 | }); 30 | -------------------------------------------------------------------------------- /test/readAllPOAsObjectSync.test.js: -------------------------------------------------------------------------------- 1 | import readAllPOAsObjectSync, { 2 | DEFAULT_MAPPER as defaultMapper, 3 | } from '../src/readAllPOAsObjectSync'; 4 | 5 | it('should return a function', () => { 6 | expect(typeof readAllPOAsObjectSync).toBe('function'); 7 | }); 8 | 9 | it('should return locale when DEFAULT_MAPPER w/o prefix', () => { 10 | expect(defaultMapper('mcs-public.zh-CN.po')).toBe('zh-CN'); 11 | expect(defaultMapper('mcs-public.zh-TW.po')).toBe('zh-TW'); 12 | expect(defaultMapper('mcs-public.en.po')).toBe('en'); 13 | 14 | expect( 15 | defaultMapper('./node_modules/mcs-translation/po/mcs-public.zh-cn.po'), 16 | ).toBe('zh-cn'); 17 | expect( 18 | defaultMapper('./node_modules/mcs-translation/po/mcs-public.zh-tw.po'), 19 | ).toBe('zh-tw'); 20 | expect( 21 | defaultMapper('./node_modules/mcs-translation/po/mcs-public.en.po'), 22 | ).toBe('en'); 23 | 24 | expect(defaultMapper('./node_modules/mcs-translation/po/zh-CN.po')).toBe( 25 | 'zh-CN', 26 | ); 27 | expect(defaultMapper('./node_modules/mcs-translation/po/zh-TW.po')).toBe( 28 | 'zh-TW', 29 | ); 30 | expect(defaultMapper('./node_modules/mcs-translation/po/en.po')).toBe('en'); 31 | 32 | expect(defaultMapper('zh-CN.po')).toBe('zh-CN'); 33 | expect(defaultMapper('zh-TW.po')).toBe('zh-TW'); 34 | expect(defaultMapper('en.po')).toBe('en'); 35 | }); 36 | 37 | it('should return po object with default localeMapper', () => { 38 | expect(readAllPOAsObjectSync('./test/po/mcs-public.*.po')).toMatchSnapshot(); 39 | }); 40 | 41 | it('should return po object with custom localeMapper', () => { 42 | expect( 43 | readAllPOAsObjectSync( 44 | './test/po/*_project.po', 45 | filename => filename.split(/(\/|_)/g)[6], 46 | ), 47 | ).toMatchSnapshot(); 48 | }); 49 | -------------------------------------------------------------------------------- /test/temp/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/evenchange4/react-intl-po/85313cd481b72de481e4d07526397339821bfa70/test/temp/.gitkeep --------------------------------------------------------------------------------