├── .gitignore ├── .travis.yml ├── .vscode └── launch.json ├── LICENSE ├── README.md ├── bin └── i18nline.js ├── lib ├── call_helpers.js ├── commands.js ├── commands │ ├── base_command.js │ ├── check.js │ ├── export.js │ ├── help.js │ ├── index.js │ └── synch.js ├── errors.js ├── extensions │ └── i18n_js.js ├── extractors │ ├── i18n_js_extractor.js │ ├── translate_call.js │ └── translation_hash.js ├── i18n.js ├── i18nline.js ├── load-config.js ├── log.js ├── main.js ├── pluralize.js ├── processors │ ├── base_processor.js │ └── js_processor.js ├── template.js └── utils.js ├── package-lock.json ├── package.json └── test ├── call_helpers_test.js ├── commands ├── check_test.js ├── export_test.js └── synch_test.js ├── extensions └── i18n_js_test.js ├── extractors ├── i18n_js_extractor_test.js ├── translate_call_test.js └── translation_hash_test.js └── fixtures ├── .i18nignore ├── i18n_js ├── invalid.js └── valid.js └── skipme.js /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | /tmp 3 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | before_script: 'npm install -g grunt-cli' 2 | language: node_js 3 | node_js: 4 | - "node" 5 | - "8" 6 | - "7" 7 | - "6" 8 | - "5" 9 | - "4" 10 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "type": "node", 9 | "request": "launch", 10 | "name": "Mocha Tests", 11 | "program": "${workspaceFolder}/node_modules/mocha/bin/_mocha", 12 | "args": [ 13 | "./test/**/*_test.js", 14 | "--recursive", 15 | "-u", 16 | "tdd", 17 | "--timeout", 18 | "999999", 19 | "--colors" 20 | ], 21 | "internalConsoleOptions": "openOnSessionStart" 22 | } 23 | ] 24 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014-2016 by Stijn de Witt & Jon Jensen 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | 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, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # i18nline 2 | ### Keep your translations in line 3 | 4 | [![npm](https://img.shields.io/npm/v/i18nline.svg)](https://npmjs.com/package/i18nline) 5 | [![license](https://img.shields.io/npm/l/i18nline.svg)](https://github.com/download/i18nline/LICENSE) 6 | [![travis](https://img.shields.io/travis/Download/i18nline.svg)](https://travis-ci.org/Download/i18nline) 7 | [![greenkeeper](https://img.shields.io/david/Download/i18nline.svg)](https://greenkeeper.io/) 8 | ![mind BLOWN](https://img.shields.io/badge/mind-BLOWN-ff69b4.svg) 9 | 10 | 11 | ``` 12 | ██╗ ███╗ ██╗██╗ ██╗███╗ ██╗███████╗ 13 | ██║ ████╗ ██║██║ ██║████╗ ██║██╔════╝ 14 | ██║18 ██╔██╗ ██║██║ ██║██╔██╗ ██║█████╗ 15 | ██║ ██║╚██╗██║██║ ██║██║╚██╗██║██╔══╝ 16 | ██║ ██║ ╚████║███████╗██║██║ ╚████║███████╗ 17 | ╚═╝ ╚═╝ ╚═══╝╚══════╝╚═╝╚═╝ ╚═══╝╚══════╝ 18 | KEEP YOUR TRANSLATIONS IN LINE 19 | ``` 20 | 21 | No .js/yml translation files. Easy inline defaults. Optional keys. Easy 22 | pluralization. Wrappers for HTML-free translations. 23 | 24 | I18nline extends [i18n-js](https://github.com/fnando/i18n-js), so you can 25 | add it to an already-internationalized app that uses it. 26 | 27 | ## TL;DR 28 | 29 | i18nline lets you do stuff like this: 30 | 31 | ```javascript 32 | I18n.t("Ohai %{user}, my default translation is right here in the code. \ 33 | Inferred keys, oh my!", {user: user.name}); 34 | ``` 35 | 36 | and this: 37 | 38 | ```javascript 39 | I18n.t("*Translators* won't see any markup!", 40 | {wrappers: ['$1']}); 41 | ``` 42 | 43 | Best of all, you don't need to maintain translation files anymore; 44 | i18nline will do it for you. 45 | 46 | ## What is this? 47 | 48 | This project is a fork of Jon Jensen's 49 | [i18nline-js](https://github.com/jenseng/i18nline-js) 50 | that attempts to simplify usage by adding: 51 | * Sensible defaults 52 | * Auto-configuration of plugins 53 | * Improved documentation 54 | * CLI `help` command shows man page for the CLI 55 | * CLI `index` command generates an *index.js* file you can import 56 | * CLI `synch` command synchs all internationalization files 57 | Basically Jon did all the hard work and this project is just adding lots 58 | of sugar to make it sweeter. 59 | 60 | ## Project setup 61 | `i18nline` preprocesses your source files, generating new source files and 62 | translation files based on what it finds. To setup a project you need to: 63 | * Install `i18nline` (see next section). 64 | * Create a `script` in *package.json* to run the command-line tool. 65 | * Import `I18n` and use `I18n.t()` to render internationalized text. 66 | * Create an empty file in the `out` folder (by default: `'src/i18n'`) named 67 | `'[locale].json'` for each locale you want to support. 68 | * Run `i18nline synch` to synch the translation files and index file. 69 | * `import` the index file into your project. 70 | * Call `I18n.changeLocale` to set the locale (which loads the right 71 | translation file on demand) 72 | * Call `I18n.on` to react to the `'change'` event (e.g. by re-rendering) 73 | * Get your translators to translate all the messages :) 74 | 75 | ## Installation 76 | 77 | ```sh 78 | npm install --save i18nline 79 | ``` 80 | 81 | i18nline has a dependency on i18n-js, so it will be installed automatically. 82 | 83 | ## Create a `script` to run the command-line tool 84 | `i18nline` comes with a command-line tool. This tool is written in Javascript 85 | and can be executed by Node JS. All you need to do to be able to use it is 86 | expose it via a `script` in your *package.json* (recommended), or install 87 | `i18nline` globally using the `-g` flag for `npm install`. The recommended 88 | approach is via a `script` in *package.json* because this means you only need 89 | to install `i18nline` as a normal dependency of your project. 90 | 91 | Add a script with the command `i18nline synch` to *package.json*: 92 | 93 | ```json 94 | { 95 | "scripts": { 96 | "i18n": "i18nline synch" 97 | } 98 | } 99 | ``` 100 | 101 | You can now invoke this command using `npm run`: 102 | 103 | ```sh 104 | $ npm run i18n 105 | ``` 106 | 107 | Alternatively, you can expose the raw command: 108 | 109 | ```json 110 | { 111 | "scripts": { 112 | "i18nline": "i18nline" 113 | } 114 | } 115 | ``` 116 | 117 | Then pass arguments via `npm run`: 118 | 119 | ```sh 120 | $ npm run i18nline -- synch 121 | ``` 122 | 123 | The extra dashes here are used to tell `npm run` that all arguments following 124 | the dashes should be passed on to the script. 125 | 126 | ## Import `I18n` and use `I18n.t()` to render internationalized text. 127 | 128 | i18nline adds some extensions to the i18n-js runtime. If you require i18n-js 129 | via i18nline, these will be added automatically for you: 130 | 131 | ```js 132 | var I18n = require('i18nline'); 133 | // Ready to rock! 134 | ``` 135 | 136 | Alternatively, you can add i18n to your app any way you like and apply the 137 | extensions manually: 138 | 139 | ```js 140 | var I18n = // get it from somewhere... script tag or whatever 141 | // add the runtime extensions manually 142 | require('i18nline/lib/extensions/i18n_js')(I18n); 143 | ``` 144 | 145 | Every file that needs to translate stuff needs to get access to the `I18n` 146 | object somehow. You can add a require call to every such file (recommended), 147 | use `I18n` from the global sope, use some Webpack loader to add the import 148 | or whatever. The choice is yours. 149 | 150 | Once `I18n` is available, you can use its `I18n.t()` function to render 151 | internationalized text: 152 | 153 | ```js 154 | console.info(I18n.t('This text will be internationalized')); 155 | ``` 156 | 157 | > i18nline will preprocess your source, extracting all calls to `I18n.t`. For this 158 | reason, you should not rename the `I18n` object, or alias the method etc. 159 | 160 | ## Create an empty file for each locale 161 | Adding support for a locale is as simple as adding an empty file 162 | named `'[locale].json'` to the `out` folder and running `i18nline synch`. 163 | You still need to translate the text of course! 164 | 165 | > If you use Webpack with Hot Module Replacement (HMR) enabled, you can 166 | change the translations while your app is running and the changes will 167 | be picked up automatically. 168 | 169 | ## Run `i18nline synch` to synch the translation files 170 | Using the script you created before, run `i18nline synch`: 171 | 172 | ```sh 173 | $ npm run i18n 174 | ``` 175 | 176 | This will create/synch a bunch of files in the `out` folder: 177 | * `default.json`: Contains the default translations extracted from the source code 178 | * `en.json`: Contains the messages for the default locale (assuming that is `'en'`) 179 | * `de.json`: Assuming you added an empty file `de.json`, it will be synched by `i18nline`. 180 | * `index.js`: Index file that you can import into your project. 181 | 182 | > The files `default.json` and `index.js` are regenerated every time, so don't change 183 | them as your changes will get lost. The translation files for the different locales 184 | are synched in a smart way, so any changes there will be respected. 185 | 186 | ## `import` the index file into your project. 187 | Since version 2, `i18nline` features an `index` command (which is also run 188 | as part of the `synch` command) that generates an index file containing the 189 | Javascript code needed to load the translation files into your project. 190 | 191 | The generated file uses dynamic `import()` statements to allow Webpack and 192 | other bundlers to perform code splitting, making sure that each translation 193 | file ends up in a separate Javascript bundle. Also, it adds support for 194 | Webpacks Hot Module Replacement. 195 | 196 | > You need to use a transpiler like [Babel](https://babeljs.io/) in 197 | combination with a bundler like [Webpack](https://webpack.js.org/) to 198 | take advantage of the code splitting and hot reloading features that the 199 | generated index file uses. If your tool chain does not support ES2015+ with 200 | dynamic `import()`, you cannot use the generated index file and need to load 201 | the translations yourself somehow. Just make sure you assign the loaded 202 | translations to `I18n.translations`. 203 | 204 | To import the index file, simply `require` or `import` it: 205 | 206 | *some file in the root of src/* 207 | ```js 208 | var I18n = require(`./i18n`); 209 | // or 210 | import I18n from './i18n'; 211 | ``` 212 | 213 | This will add a method `I18n.import` to the regular `I18n` object, 214 | with the code in it to load the translations for the given locale. 215 | If you inspect the contents of `index.js`, you will find that it 216 | contains a switch statement with similar code to load each file. 217 | That may seem redundant, but it is needed so that all the `import()` 218 | statements are statically analyzable, allowing the bundler to 219 | determine which files to include in the bundles it generates. You 220 | don't actually need to call the `I18n.import()` method yourself. 221 | It is called automatically when you use `I18n.changeLocale` 222 | (see next section). 223 | 224 | ## Call `I18n.changeLocale` to change the locale 225 | `i18nline` adds a method `changeLocale` to `I18n` that uses the 226 | method `I18n.import` (found in the generated index file) to load the 227 | translations for the locale when needed. So call `changeLocale` in 228 | your code to change the locale and the translation files will be 229 | loaded automatically when needed. 230 | 231 | ```js 232 | I18n.changeLocale('de'); 233 | ``` 234 | 235 | ## Call `I18n.on` to react to the `'change'` event 236 | `i18nline` uses [uevents](https://npmjs.com/package/uevents) to turn `I18n` 237 | into an event emitter. Whenever the locale or the translations for a 238 | locale have changed, `i18nline` emits a `'change'` event. You can add a 239 | listener for this event like so: 240 | 241 | ```js 242 | I18n.on('change', locale => { 243 | // the locale changed to the given locale, or the translations for the 244 | // given locale changed. React accordingly, e.g. by re-rendering 245 | }); 246 | ``` 247 | 248 | The docs for the [Node JS Events API](https://nodejs.org/api/events.html) 249 | explain how to remove listeners and perform other bookkeeping operations 250 | on event emitters. 251 | 252 | ## Features 253 | 254 | ### No more .js/.yml translation files 255 | 256 | Instead of maintaining .js/.yml files and doing stuff like this: 257 | 258 | ```javascript 259 | I18n.t('account_page_title'); 260 | ``` 261 | 262 | Forget the translation file and just do: 263 | 264 | ```javascript 265 | I18n.t('account_page_title', "My Account"); 266 | ``` 267 | 268 | Regular I18n options follow the (optional) default translation, so you can do 269 | the usual stuff (placeholders, etc.). 270 | 271 | #### Okay, but don't the translators need them? 272 | 273 | Sure, but *you* don't need to write them. Just run `i18nline export` 274 | to extract all default translations from your codebase and output them to 275 | `src/i18n/default.json` In addition, any translation files already present 276 | in the `out` folder are synched: any keys no longer present in the source 277 | are removed and any new keys are added. Finally this outputs an index file 278 | named `index.js` that you can `import` in your app. 279 | 280 | ### It's okay to lose your keys 281 | 282 | Why waste time coming up with keys that are less descriptive than the 283 | default translation? i18nline makes keys optional, so you can just do this: 284 | 285 | ```javascript 286 | I18n.t("My Account") 287 | ``` 288 | 289 | i18nline will create a unique key based on the translation (e.g. 290 | `'my_account'`), so you don't have to. 291 | See [inferredKeyFormat](#inferredkeyformat) for more information. 292 | 293 | This can actually be a **good thing**, because when the `default` 294 | translation changes, the key changes, which means you know you need 295 | to get it retranslated (instead of letting a now-inaccurate 296 | translation hang out indefinitely). 297 | 298 | If you are changing the meaning of the default translation, e.g. 299 | by changing "Enter your username and password to log in" to 300 | "Enter your e-mail address and password to log in", you should make 301 | the change in the source code to force a re-translation for all 302 | languages. If you are just changing the wording of the message, 303 | e.g. by changing "Enter your username and password to log in" to 304 | "Enter your username and password to sign in", you can make the 305 | change in the translation file `en.js` instead, so other languages 306 | are not affected. 307 | 308 | > Never change the file `default.json`, it is intended to 309 | accurately reflect the text that was extracted from the program 310 | source and as such it is always regenerated and not synched. 311 | 312 | ### Wrappers 313 | 314 | Suppose you have something like this in your JavaScript: 315 | 316 | ```javascript 317 | var string = 'You can lead a new discussion or \ 318 | join an existing one.'; 319 | ``` 320 | 321 | You might say "No, I'd use handlebars". Bear with me here, we're 322 | trying to make this easy for you *and* the translators :). 323 | For I18n, you might try something like this: 324 | 325 | ```javascript 326 | var string = I18n.t('You can %{lead} a new discussion or %{join} an \ 327 | existing one.', { 328 | lead: '' + I18n.t('lead') + '', 329 | join: ' + 'I18n.t('join') + '') 330 | }); 331 | ``` 332 | 333 | This is not great, because: 334 | 335 | 1. There are three strings to translate. 336 | 2. When translating the verbs, the translator has no context for where it's 337 | being used... Is "lead" a verb or a noun? 338 | 3. Translators have their hands somewhat tied as far as what is inside the 339 | links and what is not. 340 | 341 | So you might try this instead: 342 | 343 | ```javascript 344 | var string = I18n.t('You can lead a new \ 345 | discussion or join an existing one.', { 346 | leadUrl: "/new", 347 | joinUrl: "/search" 348 | }); 349 | ``` 350 | 351 | This isn't much better, because now you have HTML in your translations. 352 | If you want to add a class to the link, you have to go update all the 353 | translations. A translator could accidentally break your page (or worse, 354 | cross-site script it). 355 | 356 | So what do you do? 357 | 358 | i18nline lets you specify wrappers, so you can keep HTML out the translations, 359 | while still just having a single string needing translation: 360 | 361 | ```javascript 362 | var string = I18n.t('You can *lead* a new discussion or **join** an \ 363 | existing one.', { 364 | wrappers: [ 365 | '$1', 366 | '", raw_input: ""}); 384 | => "If you type <input> you get " 385 | ``` 386 | 387 | If any interpolated value or wrapper is HTML-safe, everything else will be HTML- 388 | escaped. 389 | 390 | ### Inline Pluralization Support 391 | 392 | Pluralization can be tricky, but i18n.js gives you some flexibility. 393 | i18nline brings this inline with a default translation object, e.g. 394 | 395 | ```javascript 396 | I18n.t({one: "There is one light!", other: "There are %{count} lights!"}, 397 | {count: picard.visibleLights.length}); 398 | ``` 399 | 400 | Note that the `count` interpolation value needs to be explicitly set when doing 401 | pluralization. 402 | 403 | If you just want to pluralize a single word, there's a shortcut: 404 | 405 | ```javascript 406 | I18n.t("person", {count: users.length}); 407 | ``` 408 | 409 | This is equivalent to: 410 | 411 | ```javascript 412 | I18n.t({one: "1 person", other: "%{count} people"}, 413 | {count: users.length}); 414 | ``` 415 | 416 | ## Configuration 417 | 418 | For most projects, no configuration should be needed. 419 | The default configuration should work without changes, unless: 420 | 421 | * You have source files in a directory that is in the default 422 | `ignoreDirectories`, or in the root of your project (not recommended) 423 | * You have source files that don't match the default `patterns` 424 | * You need the output to go some place other than the default 425 | `out` folder of `'src/i18n/'` 426 | * You have i18nline(r) `plugins` you want to configure that are 427 | not recognized by the auto-configuration feature 428 | 429 | If you find you need to change the configuration, you can configure 430 | i18nline through *package.json*, *i18nline.rc* or command line arguments. 431 | 432 | If multiple sources of configuration are present, they will be 433 | applied in this order, with the last option specified overwriting 434 | the previous settings: 435 | 436 | * Defaults 437 | * package.json 438 | * .i18nrc file 439 | * CLI arguments 440 | 441 | In your *package.json*, create a key named `"i18n"` and 442 | specify your project's global configuration settings there. 443 | 444 | *package.json* 445 | ```json 446 | { 447 | "name": "my-module", 448 | "version": "1.0.0", 449 | 450 | "i18n": { 451 | "settings": "go here" 452 | } 453 | } 454 | ``` 455 | 456 | > If i18nline detects that your project is using 457 | [pkgcfg](https://npmjs.com/package/pkgcfg), it will load 458 | `package.json` using it, enabling all dynamic goodness. 459 | 460 | Or, if you prefer, you can create a `.i18nrc` options file in the root 461 | of your project. 462 | 463 | You can also pass some configuration options directly to the CLI. 464 | 465 | For your convenience, this is the default configuration that will 466 | be used if you supply no custom configuration: 467 | 468 | ```json 469 | { 470 | "basePath": ".", 471 | "ignoreDirectories": ["node_modules", "bower_components", ".git", "dist", "build"], 472 | "patterns": ["**/*.js", "**/*.jsx"], 473 | "ignorePatterns": [], 474 | "out": "src/i18n", 475 | "inferredKeyFormat": "underscored_crc32", 476 | "underscoredKeyLength": 50, 477 | "defaultLocale": "en", 478 | } 479 | ``` 480 | 481 | ### Options 482 | 483 | #### basePath 484 | String. Defaults to `"."`. 485 | The base path (relative to the current directory). `out`, 486 | `directories`, `ignoreDirectories`, `patterns`, `ignorePatterns` 487 | and any ignore patterns coming from `.i18nignore` files are 488 | interpreted as being relative to `basePath`. 489 | 490 | #### directories 491 | Array of directories, or a String containing a comma separated 492 | list of directories. Defaults to `undefined`. 493 | Only files in these directories will be processed. 494 | 495 | > If no directories are specified, the i18nline CLI will try to 496 | auto-configure this setting with all directories in `basePath` 497 | that are not excluded by `ignoreDirectories`. This mostly works 498 | great, but if you have source files in the root of your project, 499 | they won't be found this way. Set `directories` to `"."` to force 500 | the processing to start at the root (not recommended as it may 501 | be very slow). 502 | 503 | #### ignoreDirectories 504 | Array of directories, or a String containing a comma separated 505 | list of directories. Defaults to `['node_modules', 'bower_components', '.git', 'dist']`. 506 | These directories will not be processed. 507 | 508 | #### patterns 509 | Array of pattern strings, or a String containing a comma separated 510 | list of pattern(s). Defaults to `["**/*.js", "**/*.jsx"]`. Only 511 | files matching these patterns will be processed. 512 | 513 | > Note that for your convenience, the defaults include .jsx files 514 | 515 | #### ignorePatterns 516 | Array of pattern strings, or a String containing a comma separated 517 | list of patterns. Defaults to `[]`. Files matching these patterns 518 | will be ignored, even if they match `patterns`. 519 | 520 | #### out 521 | String. Defaults to `'src/i18n'`. 522 | In case `out` ends with `'.json'`, the `export` command will export 523 | the default translations to this file and the `synch` command will 524 | just perform an export. Otherwise, `out` is interpreted as a folder 525 | to be used by the `synch` command to synch the translations and the 526 | file with the default translations will be named `default.json`. 527 | 528 | #### outputFile 529 | String. Alias for `out`. **deprecated**. 530 | In previous versions of `i18nline`, `outputFile` was used to 531 | indicate where to export the default translations. However, 532 | starting with version 2, `i18nline` now supports synching the 533 | entire translations folder, so `out` is preferred to be set 534 | to a folder, making the name `outputFile` confusing. As long as 535 | your outputFile is set to some path ending in `'.json'`, your 536 | old configuration will continue to work for all versions in the 537 | 2.x branch, but may stop working at version 3+. If you relied 538 | on the default, consider adopting the new default filename of 539 | `default.json` i.s.o. `en.json`, or explicitly set `out` to 540 | `'i18n/en.json'` (the old default, not recommended). 541 | When you set this option, a deprecation warning is logged. 542 | 543 | #### inferredKeyFormat 544 | String. Defaults to `"underscored_crc32"`. 545 | When no key was specified for a translation, `i18nline` will infer one 546 | from the default translation using the format specified here. Available 547 | formats are: `"underscored"` and `"underscored_crc32"`, where the second 548 | form uses a checksum over the whole message to ensure that changes in the 549 | message beyond the `underscoredKeyLength` limit will still result in the 550 | key changing. 551 | 552 | > If `inferredKeyFormat` is set to an unknown format, the unaltered default 553 | translation string is used as the key (not recommended). 554 | 555 | #### underscoredKeyLength 556 | Number. Defaults to `50`. The maximum length the inferred `underscored` 557 | key derived of a message will be. If the message is longer than this 558 | limit, changes in the message will only have an effect on the inferred 559 | key if `inferredKeyFormat` is set to `underscored_crc32`. In that 560 | case the checksum is appended to the underscored key (separated by an 561 | underscore), making the total max key length `underscoredKeyLength + 9`. 562 | 563 | ## Command Line Utility 564 | 565 | ### i18nline check 566 | 567 | Ensures that there are no problems with your translate calls (e.g. missing 568 | interpolation values, reusing a key for a different translation, etc.). **Go 569 | add this to your Jenkins/Travis tasks.** 570 | 571 | ### i18nline export 572 | 573 | Does an `i18nline check`, and then extracts all default translations from your 574 | codebase. If `out` ends with `'.json'`, it outputs the default translations to 575 | the configured file. Otherwise it assumes `out` is a folder and saves the default translations in this folder in a file named `default.json`. 576 | 577 | ### i18nline index 578 | 579 | Generates an index file named `index.js` that you can `import` into your project 580 | and that takes care of (hot re-)loading the individual translations when needed. 581 | 582 | ### i18nline synch 583 | 584 | Does an `i18nline check`, and then extracts all default translations from your 585 | codebase. It then runs `i18nline export` to export the default translations. 586 | If `out` ends with `'.json'` it prints a warning and stops. Otherwise it checks 587 | if a translation file for the default locale (normally `'en'`) is found. If not, 588 | it generates an empty file for it to be synched in the next step. Then, it reads 589 | all translation files present in the folder (expected to be named `'[locale].json'`, 590 | e.g. `'fr.json'`, `'de.json'`, etc.) and synchs them, removing keys that are no 591 | longer in use and adding new keys with their value set to the default translation 592 | for that key. Finally, it runs `i18nline index` to generate an index file that 593 | you can `import` into your project. 594 | 595 | Adding support for a new locale can be done by adding an empty file for that 596 | locale and running `i18nline synch` so it will populate the new file with all 597 | default translations. 598 | 599 | > The synch command works best when you use inferred keys with the 600 | `inferredKeyFormat` set to `"underscored_crc32"` (the default). 601 | 602 | ### i18nline help 603 | 604 | Prints this message: 605 | 606 | ``` 607 | ██╗ ███╗ ██╗██╗ ██╗███╗ ██╗███████╗ 608 | ██║ ████╗ ██║██║ ██║████╗ ██║██╔════╝ 609 | ██║18 ██╔██╗ ██║██║ ██║██╔██╗ ██║█████╗ 610 | ██║ ██║╚██╗██║██║ ██║██║╚██╗██║██╔══╝ 611 | ██║ ██║ ╚████║███████╗██║██║ ╚████║███████╗ 612 | ╚═╝ ╚═╝ ╚═══╝╚══════╝╚═╝╚═╝ ╚═══╝╚══════╝ 613 | keep your translations in line 614 | 615 | Usage 616 | 617 | i18nline [options] 618 | 619 | Commands 620 | 621 | check Performs a dry-run with all checks, but does not write any files 622 | export Performs a check, then exports the default translation file 623 | index Generates an index file you can import in your program 624 | synch Synchronizes all generated files with the source code 625 | help Prints this help screen 626 | 627 | Options 628 | 629 | You can set/override all of i18nline's configuration options on the command line. 630 | SEE: https://github.com/download/i18nline#configuration 631 | In addition these extra options are available in the CLI: 632 | 633 | -o Alias for --out (SEE config docs) 634 | --only Only process a single file/directory/pattern 635 | --silent Don't log any messages 636 | -s Alias for --silent 637 | 638 | Examples 639 | 640 | $ i18nline check --only=src/some-file.js 641 | > Only check the given file for errors 642 | 643 | $ i18nline export --directory=src --patterns=**/*.js,**/*.jsx 644 | > Export all translations in `src` directory from .js and .jsx files 645 | > to default output file src/i18n/default.json 646 | 647 | $ i18nline export -o=translations 648 | > Export all translations in any directory but the ignored ones, from 649 | > .js and .jsx files to the given output file translations/default.json 650 | 651 | See what's happening 652 | 653 | i18nline uses ulog for it's logging. The default level is info. To change it: 654 | $ LOG=debug (or trace, log, info, warn, error) 655 | Now, i18nline will log any messages at or above the set level 656 | ``` 657 | 658 | #### .i18nignore and more 659 | 660 | By default, the check and export commands will look for inline translations 661 | in any .js files. You can tell it to always skip certain files/directories/patterns 662 | by creating a .i18nignore file. The syntax is the same as 663 | [.gitignore](http://www.kernel.org/pub/software/scm/git/docs/gitignore.html), 664 | though it supports 665 | [a few extra things](https://github.com/jenseng/globby#compatibility-notes). 666 | 667 | If you only want to check a particular file/directory/pattern, you can set the 668 | `--only` option when you run the command, e.g. 669 | 670 | ```bash 671 | i18nline check --only=/app/**/user* 672 | ``` 673 | 674 | ## Compatibility 675 | 676 | i18nline is compatible with i18n.js, i18nliner-js, i18nliner (ruby) etc so you can 677 | add it to an established (and already internationalized) app. Your existing 678 | translation calls, keys and translation files will still just work without modification. 679 | 680 | If you want to maximize the portability of your code across the I18n ecosystem, you 681 | should avoid including hard dependencies to any particular library in every file. One way 682 | to easily achieve that is to set `I18n` as a global. Another simple way is to make your 683 | own `i18n.js` file that just requires `'i18nline/i18n'`and sets it on `module.exports`. then 684 | you let all your modules require this file. If you ever want to change 'providers', 685 | you only need to change this file. 686 | 687 | ## Related Projects 688 | 689 | * [i18nliner (ruby)](https://github.com/jenseng/i18nliner) 690 | * [i18nliner-js](https://github.com/jenseng/i18nliner-js) 691 | * [i18nliner-handlebars](https://github.com/fivetanley/i18nliner-handlebars) 692 | * [react-i18nliner](https://github.com/jenseng/react-i18nliner) 693 | * [preact-i18nline](https://github.com/download/preact-i18nline) 694 | 695 | ## License 696 | 697 | Copyright (c) 2018 Stijn de Witt & Jon Jensen, 698 | released under the MIT license 699 | -------------------------------------------------------------------------------- /bin/i18nline.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | var minimist = require('minimist'); 4 | var extend = require('extend'); 5 | var argv = minimist(process.argv.slice(2)); 6 | 7 | var log = require('../lib/log')('i18nline', argv.s || argv.silent) 8 | 9 | var command = argv._[0]; 10 | delete argv._ 11 | var I18nline = require('../lib/main').configure(argv); 12 | I18nline.Commands.run(command, I18nline.config); 13 | -------------------------------------------------------------------------------- /lib/call_helpers.js: -------------------------------------------------------------------------------- 1 | var log = require('./log')('i18nline:call-helpers'); 2 | 3 | var pluralize = require('./pluralize'); 4 | var Utils = require('./utils'); 5 | var I18nline = require('./i18nline'); 6 | var getSlug = require('speakingurl'); 7 | var crc32 = require('crc32'); 8 | 9 | var CallHelpers = { 10 | ALLOWED_PLURALIZATION_KEYS: ["zero", "one", "few", "many", "other"], 11 | REQUIRED_PLURALIZATION_KEYS: ["one", "other"], 12 | UNSUPPORTED_EXPRESSION: [], 13 | 14 | normalizeKey: function(key) { 15 | return key; 16 | }, 17 | 18 | normalizeDefault: function(defaultValue, translateOptions) { 19 | defaultValue = CallHelpers.inferPluralizationHash(defaultValue, translateOptions); 20 | return defaultValue; 21 | }, 22 | 23 | inferPluralizationHash: function(defaultValue, translateOptions) { 24 | if (typeof defaultValue === 'string' && defaultValue.match(/^[\w-]+$/) && translateOptions && ("count" in translateOptions)) { 25 | return {one: "1 " + defaultValue, other: "%{count} " + pluralize(defaultValue)}; 26 | } 27 | else { 28 | return defaultValue; 29 | } 30 | }, 31 | 32 | isObject: function(object) { 33 | return typeof object === 'object' && object !== this.UNSUPPORTED_EXPRESSION; 34 | }, 35 | 36 | validDefault: function(allowBlank) { 37 | var defaultValue = this.defaultValue; 38 | return allowBlank && (typeof defaultValue === 'undefined' || defaultValue === null) || 39 | typeof defaultValue === 'string' || 40 | this.isObject(defaultValue); 41 | }, 42 | 43 | inferKey: function(defaultValue, translateOptions) { 44 | if (this.validDefault(defaultValue)) { 45 | defaultValue = this.normalizeDefault(defaultValue, translateOptions); 46 | if (typeof defaultValue === 'object') 47 | defaultValue = "" + defaultValue.other; 48 | return this.keyify(defaultValue); 49 | } 50 | }, 51 | 52 | keyifyUnderscored: function(string) { 53 | var key = getSlug(string, {separator: '_', lang: false}).replace(/[-_]+/g, '_'); 54 | return key.substring(0, I18nline.config.underscoredKeyLength); 55 | }, 56 | 57 | keyifyUnderscoredCrc32: function(string) { 58 | var checksum = crc32(string.length + ":" + string).toString(16); 59 | return this.keyifyUnderscored(string) + "_" + checksum; 60 | }, 61 | 62 | keyify: function(string) { 63 | switch (I18nline.config.inferredKeyFormat) { 64 | case 'underscored': 65 | return this.keyifyUnderscored(string); 66 | case 'underscored_crc32': 67 | return this.keyifyUnderscoredCrc32(string); 68 | default: 69 | return string; 70 | } 71 | }, 72 | 73 | keyPattern: /^(\w+\.)+\w+$/, 74 | 75 | /** 76 | * Possible translate signatures: 77 | * 78 | * key [, options] 79 | * key, default_string [, options] 80 | * key, default_object, options 81 | * default_string [, options] 82 | * default_object, options 83 | **/ 84 | isKeyProvided: function(keyOrDefault, defaultOrOptions, maybeOptions) { 85 | if (typeof keyOrDefault === 'object') 86 | return false; 87 | if (typeof defaultOrOptions === 'string') 88 | return true; 89 | if (maybeOptions) 90 | return true; 91 | if (typeof keyOrDefault === 'string' && keyOrDefault.match(CallHelpers.keyPattern)) 92 | return true; 93 | return false; 94 | }, 95 | 96 | isPluralizationHash: function(object) { 97 | var pKeys; 98 | return this.isObject(object) && 99 | (pKeys = Utils.keys(object)) && 100 | pKeys.length > 0 && 101 | Utils.difference(pKeys, this.ALLOWED_PLURALIZATION_KEYS).length === 0; 102 | }, 103 | 104 | inferArguments: function(args, meta) { 105 | if (args.length === 2 && typeof args[1] === 'object' && args[1].defaultValue) 106 | return args; 107 | 108 | var hasKey = this.isKeyProvided.apply(this, args); 109 | if (meta) 110 | meta.inferredKey = !hasKey; 111 | if (!hasKey) 112 | args.unshift(null); 113 | 114 | var defaultValue = null; 115 | var defaultOrOptions = args[1]; 116 | if (args[2] || typeof defaultOrOptions === 'string' || this.isPluralizationHash(defaultOrOptions)) 117 | defaultValue = args.splice(1, 1)[0]; 118 | if (args.length === 1) 119 | args.push({}); 120 | var options = args[1]; 121 | if (defaultValue) 122 | options.defaultValue = defaultValue; 123 | if (!hasKey) 124 | args[0] = this.inferKey(defaultValue, options); 125 | return args; 126 | }, 127 | 128 | applyWrappers: function(string, wrappers) { 129 | var i; 130 | var len; 131 | var keys; 132 | if (typeof wrappers === 'string') 133 | wrappers = [wrappers]; 134 | if (wrappers instanceof Array) { 135 | for (i = wrappers.length; i; i--) 136 | string = this.applyWrapper(string, new Array(i + 1).join("*"), wrappers[i - 1]); 137 | } 138 | else { 139 | keys = Utils.keys(wrappers); 140 | keys.sort(function(a, b) { return b.length - a.length; }); // longest first 141 | for (i = 0, len = keys.length; i < len; i++) 142 | string = this.applyWrapper(string, keys[i], wrappers[keys[i]]); 143 | } 144 | return string; 145 | }, 146 | 147 | applyWrapper: function(string, delimiter, wrapper) { 148 | var escapedDelimiter = Utils.regexpEscape(delimiter); 149 | var pattern = new RegExp(escapedDelimiter + "(.*?)" + escapedDelimiter, "g"); 150 | return string.replace(pattern, wrapper); 151 | } 152 | }; 153 | 154 | module.exports = CallHelpers; 155 | 156 | log.debug('Initialized ' + log.name); 157 | -------------------------------------------------------------------------------- /lib/commands.js: -------------------------------------------------------------------------------- 1 | var log = require('./log')('i18nline:commands'); 2 | 3 | var fs = require('fs'); 4 | var path = require('path'); 5 | var chalk = require('chalk'); 6 | var w = chalk.white, gr = chalk.gray, grb = chalk.gray.bold, g=chalk.green, gb=chalk.green.bold; 7 | 8 | var Utils = require('./utils'); 9 | var Check = require('./commands/check'); 10 | var Export = require('./commands/export'); 11 | var Index = require('./commands/index'); 12 | var Synch = require('./commands/synch'); 13 | var Help = require('./commands/help'); 14 | 15 | function capitalize(string) { 16 | return typeof string === "string" && string ? 17 | string.slice(0, 1).toUpperCase() + string.slice(1) : 18 | string; 19 | } 20 | 21 | var Commands = { 22 | run: function(name, options) { 23 | name = name || 'help'; 24 | options = options || {}; 25 | log.log(log.name + ': ' + name, options); 26 | if (name != 'help' && !options.directories) { 27 | options.directories = autoConfigureDirectories(options); 28 | } 29 | var Command = this[capitalize(name)]; 30 | if (Command) { 31 | try { 32 | log.info(''); 33 | log.info(w(' ██╗ ███╗ ██╗██╗ ██╗███╗ ██╗███████╗ ')); 34 | log.info(w(' ██║ ████╗ ██║██║ ██║████╗ ██║██╔════╝ ')); 35 | log.info(w(' ██║') + gr('18') + w(' ██╔██╗ ██║██║ ██║██╔██╗ ██║█████╗ ')); 36 | log.info(w(' ██║ ██║╚██╗██║██║ ██║██║╚██╗██║██╔══╝ ')); 37 | log.info(w(' ██║ ██║ ╚████║███████╗██║██║ ╚████║███████╗ ')); 38 | log.info(w(' ╚═╝ ╚═╝ ╚═══╝╚══════╝╚═╝╚═╝ ╚═══╝╚══════╝ ')); 39 | log.info(grb(' keep your translations in line ')); 40 | log.info(''); 41 | return (new Command(options)).run(); 42 | } catch (e) { 43 | log.error(log.name + ': ERROR: ' + name + ' failed', e); 44 | } 45 | } else { 46 | log.error(log.name + ": ERROR: unknown command " + name + "\n"); 47 | } 48 | return false; 49 | }, 50 | 51 | Check: Check, 52 | Export: Export, 53 | Synch: Synch, 54 | Help: Help, 55 | Index: Index, 56 | }; 57 | 58 | function autoConfigureDirectories(options) { 59 | var base = path.resolve(process.cwd(), options.basePath); 60 | return fs.readdirSync(base).filter(function(file) { 61 | return ( 62 | fs.statSync(path.resolve(base, file)).isDirectory() && 63 | options.ignoreDirectories.indexOf(file) === -1 64 | ) 65 | }); 66 | } 67 | 68 | module.exports = Commands; 69 | 70 | log.debug('Initialized ' + log.name); 71 | -------------------------------------------------------------------------------- /lib/commands/base_command.js: -------------------------------------------------------------------------------- 1 | var log = require('../log')('i18nline:commands:base'); 2 | var chalk = require("chalk"); 3 | var fs = require('fs'); 4 | var extend = require('extend'); 5 | var I18nline = require('../i18nline'); 6 | 7 | function BaseCommand(options) { 8 | if (options.silent) log.level = log.NONE; 9 | options.out = options.o || options.out || options.outputFile; 10 | if (options.outputFile) { 11 | log.warn(chalk.yellow('i18nline: Option `outputFile` is deprecated. Prefer `out` instead.')); 12 | } 13 | options = extend({}, I18nline.config, options); 14 | options.patterns = typeof options.patterns == 'string' ? options.patterns.split(',') : options.patterns || []; 15 | options.ignorePatterns = typeof options.ignorePatterns == 'string' ? options.ignorePatterns.split(',') : options.ignorePatterns || []; 16 | options.directories = typeof options.directories == 'string' ? options.directories.split(',') : options.directories; 17 | this.options = options; 18 | } 19 | 20 | module.exports = BaseCommand; 21 | 22 | log.debug('Initialized ' + log.name); 23 | -------------------------------------------------------------------------------- /lib/commands/check.js: -------------------------------------------------------------------------------- 1 | var log = require('../log')('i18nline:commands:check'); 2 | 3 | var chalk = require("chalk"); 4 | var r = chalk.red, g = chalk.green, gr = chalk.gray; 5 | 6 | var TranslationHash = require("../extractors/translation_hash"); 7 | var BaseCommand = require("./base_command"); 8 | var JsProcessor = require("../processors/js_processor"); 9 | 10 | 11 | function sum(array, prop) { 12 | var total = 0; 13 | for (var i = 0, len = array.length; i < len; i++) { 14 | total += array[i][prop]; 15 | } 16 | return total; 17 | } 18 | 19 | function Check(options) { 20 | if (options.silent) log.level = log.NONE; 21 | BaseCommand.call(this, options); 22 | this.errors = []; 23 | this.translations = new this.TranslationHash(); 24 | this.setUpProcessors(); 25 | } 26 | 27 | Check.prototype = Object.create(BaseCommand.prototype); 28 | Check.prototype.constructor = Check; 29 | Check.prototype.TranslationHash = TranslationHash; 30 | 31 | Check.prototype.setUpProcessors = function() { 32 | this.processors = []; 33 | for (var key in Check.processors) { 34 | var Processor = Check.processors[key]; 35 | this.processors.push( 36 | new Processor(this.translations, { 37 | translations: this.translations, 38 | checkWrapper: this.checkWrapper.bind(this), 39 | only: this.options.only, 40 | patterns: this.options.patterns, 41 | ignorePatterns: this.options.ignorePatterns, 42 | directories: this.options.directories, 43 | ignoreDirectories: this.options.ignoreDirectories, 44 | }) 45 | ); 46 | } 47 | }; 48 | 49 | Check.prototype.checkFiles = function() { 50 | for (var i = 0; i < this.processors.length; i++) { 51 | this.processors[i].checkFiles(); 52 | } 53 | }; 54 | 55 | Check.prototype.checkWrapper = function(file, checker) { 56 | try { 57 | var found = checker(file); 58 | if (found) {log.info(g("+" + found) + (found < 10 ? ' ' : ' ') + gr(file));} 59 | return found; 60 | } catch (e) { 61 | this.errors.push(e.message + "\n" + file); 62 | log.error(r("ERR" + this.errors.length) + (this.errors.length < 10 ? ' ' : ' ') + gr(file)); 63 | return 0; 64 | } 65 | }; 66 | 67 | Check.prototype.isSuccess = function() { 68 | return !this.errors.length; 69 | }; 70 | 71 | Check.prototype.printSummary = function() { 72 | var processors = this.processors; 73 | var summary; 74 | var errors = this.errors; 75 | var errorsLen = errors.length; 76 | var i; 77 | 78 | var translationCount = sum(processors, 'translationCount'); 79 | var fileCount = sum(processors, 'fileCount'); 80 | 81 | for (i = 0; i < errorsLen; i++) { 82 | log.error("\nERR" + (i+1) + ")\n" + r(errors[i])); 83 | } 84 | summary = "\n" + fileCount + " files, " + translationCount + " strings, " + errorsLen + " failures\n"; 85 | if (this.isSuccess()) {log.info(g(summary));} 86 | else {log.error(r(summary));} 87 | }; 88 | 89 | Check.prototype.run = function() { 90 | var now = new Date(); 91 | this.startTime = now.getTime(); 92 | 93 | log[!this.sub && this.constructor === Check ? 'info' : 'debug']( 94 | 'Checking source files for errors\n' + 95 | gr('Tip: Add this task to your continuous build.\n') 96 | ); 97 | 98 | this.checkFiles(); 99 | this.printSummary(); 100 | var elapsed = (new Date()).getTime() - this.startTime; 101 | log[!this.sub && this.constructor === Check ? 'info' : 'debug']( 102 | "\nCheck finished " + (this.isSuccess() ? "" : "with errors ") + "in " + (elapsed / 1000) + " seconds\n" 103 | ); 104 | return this.isSuccess(); 105 | }; 106 | 107 | Check.processors = {JsProcessor: JsProcessor}; 108 | 109 | module.exports = Check; 110 | 111 | log.debug('Initialized ' + log.name); 112 | -------------------------------------------------------------------------------- /lib/commands/export.js: -------------------------------------------------------------------------------- 1 | var log = require('../log')('i18nline:commands:export'); 2 | var path = require('path'); 3 | var fs = require('fs'); 4 | var mkdirp = require('mkdirp'); 5 | var extend = require('extend'); 6 | var chalk = require("chalk"); 7 | var r = chalk.red, g = chalk.green, gr = chalk.gray; 8 | 9 | var I18nline = require('../i18nline'); 10 | var Check = require('./check'); 11 | 12 | var template = fs.readFileSync(path.resolve(__dirname, '../template.js')).toString(); 13 | 14 | function Export(options) { 15 | if (options.silent) log.level = log.NONE; 16 | Check.call(this, options); 17 | } 18 | 19 | Export.prototype = Object.create(Check.prototype); 20 | Export.prototype.constructor = Export; 21 | 22 | Export.prototype.run = function() { 23 | var now = new Date(); 24 | this.startTime = now.getTime(); 25 | 26 | var locale = this.options.defaultLocale || I18nline.config.defaultLocale; 27 | var basePath = this.options.basePath || I18nline.config.basePath; 28 | this.out = path.resolve(basePath, this.options.out || I18nline.config.out); 29 | var outputFolder = path.extname(this.out) == '.json' ? path.dirname(this.out) : this.out; 30 | 31 | log[!this.sub && this.constructor === Export ? 'info' : 'debug']( 32 | 'Exporting default translations to ' + (this.out.endsWith('.json') ? this.out : path.join(this.out, 'default.json')) + '\n' 33 | ); 34 | 35 | Check.prototype.run.call(this); 36 | 37 | if (this.isSuccess()) { 38 | var translations = {}; 39 | translations[locale] = Object.keys(this.translations.translations).sort() 40 | .reduce(function(r,k){return ((r[k] = this.translations.translations[k]) && r) || r}.bind(this), {}); 41 | 42 | try { 43 | mkdirp.sync(outputFolder); 44 | var def = path.extname(this.out) == '.json' ? this.out : path.resolve(outputFolder, 'default.json'); 45 | 46 | this.oldDefaults = {}; 47 | if (fs.existsSync(def)) { 48 | try { 49 | this.oldDefaults = JSON.parse(fs.readFileSync(def)); 50 | } catch(e) { 51 | this.errors.push(e.message + "\n" + def); 52 | log.error(r("ERR" + this.errors.length) + (this.errors.length < 10 ? ' ' : ' ')); 53 | } 54 | } 55 | 56 | if (this.isSuccess()) { 57 | var removed = Object.keys(this.oldDefaults[locale] || {}) 58 | .filter(function(k){return !(k in translations[locale])}).length; 59 | var added = Object.keys(translations[locale]) 60 | .filter(function(k){return !(k in (this.oldDefaults[locale] || {}))}.bind(this)).length; 61 | var status = ''; 62 | if (added || removed) { 63 | try { 64 | fs.writeFileSync(def, JSON.stringify(translations, null, 2), {encoding:'utf8',flag:'w'}); 65 | status += added > 0 ? (g('+' + added) + (added < 10 ? ' ' : ' ')) : ' '; 66 | status += removed > 0 ? (r('-' + removed) + (removed < 10 ? ' ' : ' ')) : ' '; 67 | } catch(e) { 68 | this.errors.push(e.message + "\n" + def); 69 | status = r("ERR" + this.errors.length) + (this.errors.length < 10 ? ' ' : ' '); 70 | } 71 | log.info(status + gr(def)); 72 | } 73 | } 74 | 75 | for (var i=0,e; e=this.errors[i]; i++) { 76 | log.error('\nERR' + (i+1) + '\n' + e); 77 | } 78 | } catch(e) { 79 | this.errors.push(r(e.message + "\n" + def)); 80 | log.error(r("ERR" + this.errors.length) + (this.errors.length < 10 ? ' ' : ' ')); 81 | for (var i=0,e; e=this.errors[i]; i++) { 82 | log.error('\nERR' + (i+1) + '\n' + e); 83 | } 84 | } 85 | } 86 | var elapsed = (new Date()).getTime() - this.startTime; 87 | log[!this.sub && this.constructor === Export ? 'info' : 'debug']( 88 | "\nExport finished " + (this.isSuccess() ? "" : "with errors ") + "in " + (elapsed / 1000) + " seconds\n" 89 | ); 90 | return this.isSuccess(); 91 | }; 92 | 93 | module.exports = Export; 94 | 95 | log.debug('Initialized ' + log.name); 96 | -------------------------------------------------------------------------------- /lib/commands/help.js: -------------------------------------------------------------------------------- 1 | var log = require('../log')('i18nline:commands:help'); 2 | 3 | var fs = require('fs'); 4 | var mkdirp = require('mkdirp'); 5 | var chalk = require('chalk'); 6 | var wb = chalk.white.bold, gr = chalk.gray, grb = chalk.gray.bold, g=chalk.green, gb=chalk.green.bold; 7 | 8 | var BaseCommand = require('./base_command'); 9 | var I18nline = require('../../lib/i18nline'); 10 | 11 | 12 | function Help(options) { 13 | if (options.silent) log.level = log.NONE; 14 | BaseCommand.call(this, options); 15 | } 16 | 17 | Help.prototype = Object.create(BaseCommand.prototype); 18 | Help.prototype.constructor = Help; 19 | 20 | Help.prototype.run = function() { 21 | log.info(wb('Usage')); 22 | log.info(''); 23 | log.info(gb('i18nline [options]')) 24 | log.info(''); 25 | log.info(wb('Commands')) 26 | log.info(''); 27 | log.info('check ' + gr('Performs a dry-run with all checks, but does not write any files')); 28 | log.info('export ' + gr('Performs a check, then exports the default translation file')); 29 | log.info('index ' + gr('Generates an index file you can import in your program')); 30 | log.info('synch ' + gr('Synchronizes all generated files with the source code')); 31 | log.info('help ' + gr('Prints this help screen')); 32 | log.info(''); 33 | log.info(wb('Options')); 34 | log.info(''); 35 | log.info(gr('You can set/override all of i18nline\'s configuration options on the command line.')); 36 | log.info(grb('SEE: ') + g('https://github.com/download/i18nline#configuration')); 37 | log.info(gr('In addition these extra options are available in the CLI:\n')); 38 | log.info('-o ' + gr('Alias for --out (SEE config docs)')); 39 | log.info('--only ' + gr('Only process a single file/directory/pattern')); 40 | log.info('--silent ' + gr('Don\'t log any messages')); 41 | log.info('-s ' + gr('Alias for --silent')); 42 | log.info(''); 43 | log.info(wb('Examples')); 44 | log.info(''); 45 | log.info(gr('$ ') + 'i18nline check --only=src/some-file.js'); 46 | log.info(gr('> Only check the given file for errors')); 47 | log.info(''); 48 | log.info(gr('$ ') + 'i18nline export --directory=src --patterns=**/*.js,**/*.jsx'); 49 | log.info(gr('> Export all translations in `src` directory from .js and .jsx files')); 50 | log.info(gr('> to default output file src/i18n/default.json')); 51 | log.info(''); 52 | log.info(gr('$ ') + 'i18nline export -o=translations'); 53 | log.info(gr('> Export all translations in any directory but the ignored ones, from')); 54 | log.info(gr('> .js and .jsx files to the given output file translations/default.json')); 55 | log.info(''); 56 | log.info(wb('See what\'s happening')); 57 | log.info(''); 58 | log.info(gr('i18nline uses ') + g('ulog') + gr(' for it\'s logging. The default level is info. To change it:')); 59 | log.info(gr('$ ') + 'LOG=debug ' + gr(' (or trace, log, info, warn, error)')); 60 | log.info(gr('Now, i18nline will log any messages at or above the set level')); 61 | log.info(''); 62 | return 0; 63 | }; 64 | 65 | module.exports = Help; 66 | 67 | log.debug('Initialized ' + log.name); 68 | -------------------------------------------------------------------------------- /lib/commands/index.js: -------------------------------------------------------------------------------- 1 | var log = require('../log')('i18nline:commands:index'); 2 | var path = require('path'); 3 | var fs = require('fs'); 4 | var mkdirp = require('mkdirp'); 5 | var chalk = require("chalk"); 6 | var r = chalk.red, g = chalk.green, gr = chalk.grey; 7 | 8 | var I18nline = require('../i18nline'); 9 | var Check = require('./check'); 10 | 11 | var template = fs.readFileSync(path.resolve(__dirname, '../template.js')).toString(); 12 | 13 | function Index(options) { 14 | if (options.silent) log.level = log.NONE; 15 | Check.call(this, options); 16 | } 17 | 18 | Index.prototype = Object.create(Check.prototype); 19 | Index.prototype.constructor = Index; 20 | 21 | Index.prototype.run = function() { 22 | var now = new Date(); 23 | this.startTime = now.getTime(); 24 | 25 | var basePath = this.options.basePath || I18nline.config.basePath; 26 | this.out = path.resolve(basePath, this.options.out || I18nline.config.out); 27 | 28 | log[!this.sub && this.constructor === Index ? 'info' : 'debug']( 29 | 'Generating index file\n' + 30 | gr('Import the generated file into your project\n') 31 | ); 32 | 33 | mkdirp.sync(this.out); 34 | var supportedLocales = fs.readdirSync(this.out) 35 | .filter(function(f){return !fs.lstatSync(path.resolve(this.out, f)).isDirectory();}.bind(this)) 36 | .filter(function(f){return path.basename(f).match(/^[a-z][a-z]_?([A-Z][A-Z])?\.json$/);}) 37 | .map(function(f){return f.substring(0, f.length - 5);}); 38 | var indexFile = path.resolve(this.out, 'index.js'); 39 | var ignoredConfigKeys = ['basePath', 'directories', 'ignoreDirectories', 'patterns', 'ignorePatterns', 'autoTranslateTags', 'neverTranslateTags', 'out', 'inferredKeyFormat', 'underscoredKeyLength', 'locales']; 40 | var configuration = [].concat( 41 | 'I18n.supportedLocales = ' + JSON.stringify(supportedLocales).replace(/"/g, "'") + ';', 42 | Object.keys(I18nline.config) 43 | .filter(function(k){return ignoredConfigKeys.indexOf(k) === -1}) 44 | .map(function(k){return 'I18n.' + k + ' = ' + JSON.stringify(I18nline.config[k]).replace(/"/g, "'") + ';'}) 45 | ); 46 | if (I18nline.config.locales) configuration = configuration.concat( 47 | Object.keys(I18nline.config.locales) 48 | .map(function(k){return 'I18n.locales.' + k + ' = ' + JSON.stringify(I18nline.config.locales[k]).replace(/"/g, "'") + ';'}) 49 | ) 50 | var imports = supportedLocales 51 | .map(function(l){return "case '" + l + "': return import(/* webpackChunkName: 'i18n." + l + "' */ './" + l + ".json');"}); 52 | var reloads = supportedLocales 53 | .map(function(l){return "module.hot.accept('./" + l + ".json', I18n.reload('" + l + "'));";}); 54 | var parts = template.split(/\/\*\[[A-Z_]?[A-Z0-9_]+\]\*\//); 55 | var script = parts[0] + 56 | configuration.join('\n') + parts[1] + 57 | imports.join('\n\t\t') + parts[2] + 58 | reloads.join('\n\t') + parts[3]; 59 | 60 | // allow outside code to process the generated script 61 | // by assigning a function to indexFilehook 62 | if (this.indexFileHook) { 63 | script = this.indexFileHook(script) 64 | } 65 | 66 | try { 67 | fs.writeFileSync(indexFile, script, {encoding:'utf8',flag:'w'}); 68 | log.info(g('index ') + gr(indexFile)); 69 | } catch(e) { 70 | this.errors.push(e.message + "\n" + def); 71 | log.error(r("ERR" + this.errors.length) + (this.errors.length < 10 ? ' ' : ' ') + gr(indexFile)); 72 | } 73 | if (this.constructor === Index) { 74 | for (var i=0,e; e=this.errors[i]; i++) { 75 | log.error('ERR' + (i+1) + '\n' + e); 76 | } 77 | } 78 | var elapsed = (new Date()).getTime() - this.startTime; 79 | log[!this.sub && this.constructor === Index ? 'info' : 'debug']( 80 | "\nIndex finished " + (this.isSuccess() ? "" : "with errors ") + "in " + (elapsed / 1000) + " seconds\n" 81 | ); 82 | return this.isSuccess(); 83 | }; 84 | 85 | module.exports = Index; 86 | 87 | log.debug('Initialized ' + log.name); 88 | -------------------------------------------------------------------------------- /lib/commands/synch.js: -------------------------------------------------------------------------------- 1 | var log = require('../log')('i18nline:commands:synch'); 2 | var path = require('path'); 3 | var fs = require('fs'); 4 | var mkdirp = require('mkdirp'); 5 | var extend = require('extend'); 6 | var chalk = require("chalk"); 7 | var r = chalk.red, g = chalk.green, gr = chalk.gray, m = chalk.magenta; 8 | 9 | var I18nline = require('../i18nline'); 10 | var Export = require('./export'); 11 | 12 | var template = fs.readFileSync(path.resolve(__dirname, '../template.js')).toString(); 13 | 14 | function Synch(options) { 15 | if (options.silent) log.level = log.NONE; 16 | Export.call(this, options); 17 | } 18 | 19 | Synch.prototype = Object.create(Export.prototype); 20 | Synch.prototype.constructor = Synch; 21 | 22 | Synch.prototype.run = function() { 23 | var now = new Date(); 24 | this.startTime = now.getTime(); 25 | log.debug(log.name + ': synch started at ' + now) 26 | 27 | var locale = this.options.defaultLocale || I18nline.config.defaultLocale; 28 | var basePath = this.options.basePath || I18nline.config.basePath; 29 | this.out = path.resolve(basePath, this.options.out || I18nline.config.out); 30 | 31 | try { 32 | if (path.extname(this.out) !== '.json') { 33 | log.info('Synching internationalization files in ' + this.out); 34 | log.info(gr("Create files here named '{locale}.json' (e.g. 'fr.json') to include them in the synching process.\n")); 35 | } 36 | else { 37 | log.warn(m('Unable to perform a synch. Option `out` is set to a file. Performing an export instead.')); 38 | log.warn(gr('Set `out` to a folder to enable synch. Current value: ') + this.out + '\n'); 39 | } 40 | 41 | Export.prototype.run.call(this); 42 | 43 | if (this.isSuccess() && path.extname(this.out) !== '.json') { 44 | var translations = {}; 45 | translations[locale] = Object.keys(this.translations.translations).sort() 46 | .reduce(function(r,k){return ((r[k] = this.translations.translations[k]) && r) || r}.bind(this), {}); 47 | 48 | var defLoc = path.resolve(this.out, locale + '.json') 49 | if (! fs.existsSync(defLoc)) { 50 | try { 51 | var empty = {}; 52 | empty[locale] = {}; 53 | fs.writeFileSync(defLoc, JSON.stringify(empty, null, 2), {encoding:'utf8',flag:'w'}); 54 | } catch(e) { 55 | this.errors.push(e.message + "\n" + defLoc); 56 | log.error(r("ERR" + this.errors.length) + (this.errors.length < 10 ? ' ' : ' ')); 57 | } 58 | } 59 | 60 | var files = fs.readdirSync(this.out) 61 | .filter(function(f){return !fs.lstatSync(path.resolve(this.out, f)).isDirectory()}.bind(this)) 62 | .filter(function(f){return path.basename(f).match(/^[a-z][a-z][_\-]?([A-Z][A-Z])?\.json$/)}); 63 | 64 | var supportedLocales = []; 65 | 66 | if (this.isSuccess()) { 67 | for (var i=0,f; f = files[i]; i++) { 68 | var synchLocale = f.substring(0, f.length - 5); 69 | supportedLocales.push(synchLocale); 70 | 71 | log.debug('Synching translations for locale ' + synchLocale); 72 | var oldData = {}; 73 | var file = path.resolve(this.out, f); 74 | try { 75 | oldData = fs.readFileSync(file); 76 | oldData = oldData && oldData.length && JSON.parse(oldData) || {}; 77 | if (! (synchLocale in oldData)) oldData[synchLocale] = {}; 78 | log.debug('Read translations for ' + synchLocale + ' from ' + f); 79 | 80 | log.debug('Compiling new translations for ' + synchLocale); 81 | var newData = {}; 82 | newData[synchLocale] = {} 83 | Object.keys(translations[locale]).sort().forEach(k => ( 84 | newData[synchLocale][k] = k in oldData[synchLocale] 85 | ? oldData[synchLocale][k] 86 | : translations[locale][k] 87 | )); 88 | 89 | log.debug('Writing translations for ' + synchLocale); 90 | var removed = Object.keys(oldData[synchLocale] || {}) 91 | .filter(function(k){return !(k in newData[synchLocale])}).length; 92 | var added = Object.keys(newData[synchLocale]) 93 | .filter(function(k){return !(k in (oldData[synchLocale] || {}))}).length; 94 | var status = ''; 95 | var file = path.resolve(this.out, f); 96 | if (added || removed) { 97 | try { 98 | fs.writeFileSync(file, JSON.stringify(newData, null, 2), {encoding:'utf8',flag:'w'}); 99 | status += added > 0 ? (g('+' + added) + (added < 10 ? ' ' : ' ')) : gr(' - '); 100 | status += removed > 0 ? (r('-' + removed) + (removed < 10 ? ' ' : ' ')) : gr(' - '); 101 | } catch(e) { 102 | this.errors.push(e.message + "\n" + file); 103 | status = (r("ERR" + this.errors.length) + (this.errors.length < 10 ? ' ' : ' ')); 104 | } 105 | log.info(status + gr(path.resolve(this.out, f))); 106 | } 107 | } catch(e) { 108 | this.errors.push(e.message + "\n" + file); 109 | log.error(r("ERR" + this.errors.length) + (this.errors.length < 10 ? ' ' : ' ')); 110 | } 111 | } 112 | 113 | var generateIndex = new I18nline.Commands.Index(this.options); 114 | generateIndex.sub = true; // ran as sub-command 115 | if (!generateIndex.run()) { 116 | this.errors.push.apply(this.errors, generateIndex.errors); 117 | } 118 | 119 | for (var i=0,e; e=this.errors[i]; i++) { 120 | log.error('ERR' + (i+1) + '\n' + e); 121 | } 122 | } 123 | } 124 | } catch(e) { 125 | this.errors.push(e.message + "\n"); 126 | for (var i=0,e; e=this.errors[i]; i++) { 127 | log.error('ERR' + (i+1) + '\n' + e); 128 | } 129 | } 130 | var elapsed = (new Date()).getTime() - this.startTime; 131 | log[!this.sub && this.constructor === Synch ? 'info' : 'debug']( 132 | "\nSynch finished " + (this.isSuccess() ? "" : "with errors ") + "in " + (elapsed / 1000) + " seconds\n" 133 | ); 134 | return this.isSuccess(); 135 | }; 136 | 137 | module.exports = Synch; 138 | 139 | log.debug('Initialized ' + log.name); 140 | -------------------------------------------------------------------------------- /lib/errors.js: -------------------------------------------------------------------------------- 1 | var log = require('./log')('i18nline:errors'); 2 | 3 | var CallHelpers = require('./call_helpers'); 4 | 5 | function wordify(string) { 6 | return string.replace(/[A-Z]/g, function(s) { 7 | return " " + s.toLowerCase(); 8 | }).trim(); 9 | } 10 | 11 | var Errors = { 12 | register: function(name) { 13 | this[name] = function(line, details) { 14 | this.line = line; 15 | if (details) { 16 | var parts = []; 17 | var part; 18 | if (typeof details === "string" || !details.length) details = [details]; 19 | for (var i = 0; i < details.length; i++) { 20 | part = details[i]; 21 | part = part === CallHelpers.UNSUPPORTED_EXPRESSION ? 22 | "" : 23 | JSON.stringify(part); 24 | parts.push(part); 25 | } 26 | details = parts.join(', '); 27 | } 28 | this.name = name; 29 | this.message = wordify(name) + " on line " + line + (details ? ": " + details : ""); 30 | }; 31 | } 32 | }; 33 | 34 | Errors.register('InvalidSignature'); 35 | Errors.register('InvalidPluralizationKey'); 36 | Errors.register('MissingPluralizationKey'); 37 | Errors.register('InvalidPluralizationDefault'); 38 | Errors.register('MissingInterpolationValue'); 39 | Errors.register('MissingCountValue'); 40 | Errors.register('InvalidOptionKey'); 41 | Errors.register('KeyAsScope'); 42 | Errors.register('KeyInUse'); 43 | 44 | module.exports = Errors; 45 | 46 | log.debug('Initialized ' + log.name); 47 | -------------------------------------------------------------------------------- /lib/extensions/i18n_js.js: -------------------------------------------------------------------------------- 1 | var log = require('../log')('i18nline:extensions:i18n'); 2 | var EventEmitter = require('uevents'); 3 | var CallHelpers = require('../call_helpers'); 4 | var Utils = require('../utils'); 5 | 6 | var extend = function(I18n) { 7 | function changed(locale) { 8 | return function() { 9 | I18n.emit('change', locale || I18n.locale); 10 | }; 11 | } 12 | 13 | if (!I18n.on) EventEmitter(I18n); 14 | 15 | /** 16 | * Changes the current locale, loading the translations if needed. 17 | * If I18n has an emit method, emits a 'change' event with the given locale. 18 | * Returns a Promise that resolves with the locale when the change is completed. 19 | * 20 | * @param {string} locale 21 | * @return {PromiseLike} 22 | */ 23 | I18n.changeLocale = function(locale) { 24 | return Promise.all( 25 | I18n.locales.get(I18n.locale = locale).map(function(locale){ 26 | return I18n.load(locale) 27 | }) 28 | ).then(changed()) 29 | }; 30 | 31 | /** 32 | * Loads the translations for locale, from cache (unless bypassCache == true) 33 | * or by calling I18n.import with the given locale. Returns a Promise. 34 | * 35 | * @param {string} locale 36 | * @param {boolean} bypassCache 37 | * @return {PromiseLike} 38 | */ 39 | I18n.load = function(locale, bypassCache) { 40 | return new Promise(function(resolve) { 41 | return I18n.translations[locale] && !bypassCache 42 | ? resolve(I18n.translations[locale]) 43 | : I18n.import(locale).then(function(x) {resolve(I18n.translations[locale] = x[locale]);}); 44 | }); 45 | }; 46 | 47 | /** 48 | * Returns a function that reloads the translations for the given locale. 49 | * The reload function bypasses the cache, making it ideal for HMR. 50 | * 51 | * @param {string} locale 52 | * @return {function} 53 | */ 54 | I18n.reload = function(locale) { 55 | return function() { 56 | I18n.load(locale, true).then(changed(locale)); 57 | } 58 | }; 59 | 60 | I18n.import = function(locale) { 61 | return Promise.reject('Implement I18n.import'); 62 | }; 63 | 64 | var htmlEscape = Utils.htmlEscape; 65 | 66 | if (!I18n.interpolateWithoutHtmlSafety) I18n.interpolateWithoutHtmlSafety = I18n.interpolate; 67 | I18n.interpolate = function(message, options) { 68 | var needsEscaping = false; 69 | var matches = message.match(this.PLACEHOLDER) || []; 70 | var len = matches.length; 71 | var match; 72 | var keys = []; 73 | var key; 74 | var i; 75 | var wrappers = options.wrappers || options.wrapper; 76 | 77 | if (wrappers) { 78 | needsEscaping = true; 79 | message = htmlEscape(message); 80 | message = CallHelpers.applyWrappers(message, wrappers); 81 | } 82 | 83 | for (i = 0; i < len; i++) { 84 | match = matches[i]; 85 | key = match.replace(this.PLACEHOLDER, "$1"); 86 | keys.push(key); 87 | if (!(key in options)) continue; 88 | if (match[1] === 'h') 89 | options[key] = new Utils.HtmlSafeString(options[key]); 90 | if (options[key] instanceof Utils.HtmlSafeString) 91 | needsEscaping = true; 92 | } 93 | 94 | if (needsEscaping) { 95 | if (!wrappers) 96 | message = htmlEscape(message); 97 | for (i = 0; i < len; i++) { 98 | key = keys[i]; 99 | if (!(key in options)) continue; 100 | options[key] = htmlEscape(options[key]); 101 | } 102 | } 103 | message = this.interpolateWithoutHtmlSafety(message, options); 104 | return needsEscaping ? new Utils.HtmlSafeString(message) : message; 105 | }; 106 | 107 | // add html-safety hint, i.e. "%h{...}" 108 | I18n.PLACEHOLDER = /(?:\{\{|%h?\{)(.*?)(?:\}\}?)/gm; 109 | 110 | I18n.CallHelpers = CallHelpers; 111 | I18n.Utils = Utils; 112 | 113 | if (!I18n.translateWithoutI18nline) I18n.translateWithoutI18nline = I18n.translate; 114 | I18n.translate = function() { 115 | var args = CallHelpers.inferArguments([].slice.call(arguments)); 116 | var key = args[0]; 117 | var options = args[1]; 118 | key = CallHelpers.normalizeKey(key, options); 119 | var defaultValue = options.defaultValue; 120 | 121 | if (defaultValue) { 122 | options.defaultValue = CallHelpers.normalizeDefault(defaultValue, options); 123 | } 124 | 125 | return this.translateWithoutI18nline(key, options); 126 | }; 127 | 128 | I18n.t = I18n.translate; 129 | return I18n; 130 | }; 131 | 132 | module.exports = extend; 133 | 134 | log.debug('Initialized ' + log.name); 135 | -------------------------------------------------------------------------------- /lib/extractors/i18n_js_extractor.js: -------------------------------------------------------------------------------- 1 | var log = require('../log')('i18nline:extractors:i18n'); 2 | 3 | var extend = require('extend'); 4 | var traverse = require('babel-traverse').default; 5 | var TranslateCall = require('./translate_call'); 6 | var CallHelpers = require('../call_helpers'); 7 | var TranslationHash = require('./translation_hash'); 8 | var I18nline = require('../i18nline'); 9 | 10 | function I18nJsExtractor(options) { 11 | this.ast = options.ast; 12 | } 13 | 14 | extend(I18nJsExtractor.prototype, CallHelpers); 15 | 16 | I18nJsExtractor.prototype.forEach = function(handler) { 17 | this.handler = handler; 18 | this.run(); 19 | delete this.handler; 20 | }; 21 | 22 | I18nJsExtractor.prototype.run = function() { 23 | if (!this.handler) { 24 | this.translations = new TranslationHash(); 25 | this.handler = this.translations.set.bind(this.translations); 26 | } 27 | 28 | traverse(this.ast, { 29 | enter: this.enter.bind(this), 30 | exit: this.exit.bind(this) 31 | }); 32 | }; 33 | 34 | I18nJsExtractor.prototype.isExtractableCall = function(node, receiver, method) { 35 | return node.type === "MemberExpression" && 36 | !node.computed && 37 | receiver.type === "Identifier" && 38 | receiver.name === "I18n" && 39 | method.type === "Identifier" && 40 | (method.name === "t" || method.name === "translate"); 41 | }; 42 | 43 | I18nJsExtractor.prototype.enter = function(node) { 44 | if (node.type === "CallExpression") 45 | this.processCall(node.node); 46 | }; 47 | 48 | I18nJsExtractor.prototype.exit = function(node) { 49 | } 50 | 51 | I18nJsExtractor.prototype.processCall = function(node) { 52 | var callee = node.callee; 53 | var receiver = callee.object; 54 | var method = callee.property; 55 | 56 | if (this.isExtractableCall(callee, receiver, method)) { 57 | var line = receiver.loc.start.line; 58 | receiver = receiver.name; 59 | method = method.name; 60 | 61 | // convert nodes to literals where possible 62 | var args = this.processArguments(node.arguments); 63 | this.processTranslateCall(line, receiver, method, args); 64 | } 65 | }; 66 | 67 | I18nJsExtractor.prototype.processArguments = function(args) { 68 | var result = []; 69 | for (var i = 0, len = args.length; i < len; i++) { 70 | result.push(this.evaluateExpression(args[i])); 71 | } 72 | return result; 73 | }; 74 | 75 | I18nJsExtractor.prototype.evaluateExpression = function(node, identifierToString) { 76 | if (node.type === "StringLiteral") 77 | return node.value; 78 | if (node.type === "Identifier" && identifierToString) 79 | return node.name; 80 | if (node.type === "ObjectExpression") 81 | return this.objectFrom(node); 82 | if (node.type === "BinaryExpression" && node.operator === "+") 83 | return this.stringFromConcatenation(node); 84 | if (node.type === "TemplateLiteral") 85 | return this.stringFromTemplateLiteral(node); 86 | return this.UNSUPPORTED_EXPRESSION; 87 | }; 88 | 89 | I18nJsExtractor.prototype.buildTranslateCall = function(line, method, args) { 90 | return new TranslateCall(line, method, args); 91 | }; 92 | 93 | I18nJsExtractor.prototype.processTranslateCall = function(line, receiver, method, args) { 94 | var call = this.buildTranslateCall(line, method, args); 95 | var translations = call.translations(); 96 | for (var i = 0, len = translations.length; i < len; i++) 97 | this.handler(translations[i][0], translations[i][1], call); 98 | }; 99 | 100 | I18nJsExtractor.prototype.objectFrom = function(node) { 101 | var object = {}; 102 | var props = node.properties; 103 | var prop; 104 | var key; 105 | for (var i = 0, len = props.length; i < len; i++) { 106 | prop = props[i]; 107 | key = this.evaluateExpression(prop.key, true); 108 | if (typeof key !== 'string') 109 | return this.UNSUPPORTED_EXPRESSION; 110 | object[key] = this.evaluateExpression(prop.value); 111 | } 112 | return object; 113 | }; 114 | 115 | I18nJsExtractor.prototype.stringFromConcatenation = function(node) { 116 | var left = this.evaluateExpression(node.left); 117 | var right = this.evaluateExpression(node.right); 118 | if (typeof left !== "string" || typeof right !== "string") 119 | return this.UNSUPPORTED_EXPRESSION; 120 | return left + right; 121 | }; 122 | 123 | I18nJsExtractor.prototype.stringFromTemplateLiteral = function(node) { 124 | if (node.quasis.length === 1 && node.quasis[0].type === "TemplateElement") { 125 | return node.quasis[0].value.raw; 126 | } 127 | return this.UNSUPPORTED_EXPRESSION; 128 | } 129 | 130 | module.exports = I18nJsExtractor; 131 | 132 | log.debug('Initialized ' + log.name); 133 | -------------------------------------------------------------------------------- /lib/extractors/translate_call.js: -------------------------------------------------------------------------------- 1 | var log = require('../log')('i18nline:extractors:translate-call'); 2 | 3 | var extend = require('extend'); 4 | var CallHelpers = require('../call_helpers'); 5 | var Errors = require('../errors'); 6 | var Utils = require('../utils'); 7 | 8 | function TranslateCall(line, method, args) { 9 | this.line = line; 10 | this.method = method; 11 | 12 | this.normalizeArguments(args); 13 | 14 | this.validate(); 15 | this.normalize(); 16 | } 17 | 18 | extend(TranslateCall.prototype, CallHelpers); 19 | 20 | TranslateCall.prototype.validate = function() { 21 | this.validateKey(); 22 | this.validateDefault(); 23 | this.validateOptions(); 24 | }; 25 | 26 | TranslateCall.prototype.normalize = function() { 27 | this.defaultValue = this.normalizeDefault(this.defaultValue, this.options || {}); 28 | }; 29 | 30 | TranslateCall.prototype.translations = function() { 31 | var key = this.key; 32 | var defaultValue = this.defaultValue; 33 | 34 | if (!defaultValue) 35 | return []; 36 | if (typeof defaultValue === 'string') 37 | return [[key, defaultValue]]; 38 | 39 | var translations = []; 40 | for (var k in defaultValue) { 41 | if (defaultValue.hasOwnProperty(k)) { 42 | translations.push([key + "." + k, defaultValue[k]]); 43 | } 44 | } 45 | return translations; 46 | }; 47 | 48 | TranslateCall.prototype.validateKey = function() {}; 49 | 50 | TranslateCall.prototype.validateDefault = function() { 51 | var defaultValue = this.defaultValue; 52 | if (typeof defaultValue === 'object') { 53 | var defaultKeys = Utils.keys(defaultValue); 54 | var dKeys; 55 | if ((dKeys = Utils.difference(defaultKeys, this.ALLOWED_PLURALIZATION_KEYS)).length > 0) 56 | throw new Errors.InvalidPluralizationKey(this.line, dKeys); 57 | if ((dKeys = Utils.difference(this.REQUIRED_PLURALIZATION_KEYS, defaultKeys)).length > 0) 58 | throw new Errors.MissingPluralizationKey(this.line, dKeys); 59 | 60 | for (var k in defaultValue) { 61 | if (defaultValue.hasOwnProperty(k)) { 62 | var v = defaultValue[k]; 63 | if (typeof v !== 'string') 64 | throw new Errors.InvalidPluralizationDefault(this.line); 65 | this.validateInterpolationValues(k, v); 66 | } 67 | } 68 | } 69 | else { 70 | this.validateInterpolationValues(this.key, this.defaultValue); 71 | } 72 | }; 73 | 74 | /** 75 | * Possible translate signatures: 76 | * 77 | * key [, options] 78 | * key, default_string [, options] 79 | * key, default_object, options 80 | * default_string [, options] 81 | * default_object, options 82 | **/ 83 | TranslateCall.prototype.normalizeArguments = function(args) { 84 | if (!args.length) 85 | throw new Errors.InvalidSignature(this.line, args); 86 | 87 | var others = this.inferArguments(args.slice(), this); 88 | var key = this.key = others.shift(); 89 | var options = this.options = others.shift(); 90 | 91 | if (others.length) 92 | throw new Errors.InvalidSignature(this.line, args); 93 | if (typeof key !== 'string') 94 | throw new Errors.InvalidSignature(this.line, args); 95 | if (options && !this.isObject(options)) 96 | throw new Errors.InvalidSignature(this.line, args); 97 | if (options) { 98 | this.defaultValue = options.defaultValue; 99 | delete options.defaultValue; 100 | } 101 | if (!this.validDefault(true)) 102 | throw new Errors.InvalidSignature(this.line, args); 103 | }; 104 | 105 | TranslateCall.prototype.validateInterpolationValues = function(key, defaultValue) { 106 | var match; 107 | var pattern = /%\{([^\}]+)\}/g; 108 | var options = this.options; 109 | var placeholder; 110 | while ((match = pattern.exec(defaultValue)) !== null) { 111 | placeholder = match[1]; 112 | if (!(placeholder in options)) 113 | throw new Errors.MissingInterpolationValue(this.line, placeholder); 114 | } 115 | }; 116 | 117 | TranslateCall.prototype.validateOptions = function() { 118 | var options = this.options; 119 | if (typeof this.defaultValue === 'object' && (!options || !options.count)) 120 | throw new Errors.MissingCountValue(this.line); 121 | if (options) { 122 | for (var k in options) { 123 | if (typeof k !== 'string') 124 | throw new Errors.InvalidOptionKey(this.line); 125 | } 126 | } 127 | }; 128 | 129 | module.exports = TranslateCall; 130 | 131 | log.debug('Initialized ' + log.name); 132 | -------------------------------------------------------------------------------- /lib/extractors/translation_hash.js: -------------------------------------------------------------------------------- 1 | var log = require('../log')('i18nline:extractors:translation-hash'); 2 | 3 | var Errors = require('../errors'); 4 | 5 | function TranslationHash() { 6 | this.translations = {}; 7 | } 8 | 9 | TranslationHash.prototype.set = function(key, value, meta) { 10 | var parts = key.split('.'); 11 | var context = this.getScope(parts.slice(0, -1), meta); 12 | var finalKey = parts[parts.length - 1]; 13 | 14 | if (context[finalKey]) { 15 | if (typeof context[finalKey] === 'object') { 16 | throw new Errors.KeyAsScope(meta.line, key); 17 | } else if (context[finalKey] !== value) { 18 | throw new Errors.KeyInUse(meta.line, key); 19 | } 20 | } 21 | context[finalKey] = value; 22 | }; 23 | 24 | TranslationHash.prototype.getScope = function(parts, meta) { 25 | var context = this.translations; 26 | var partsLen = parts.length; 27 | var key; 28 | var i; 29 | 30 | for (i = 0; i < partsLen; i++) { 31 | key = parts[i]; 32 | if (typeof context[key] === 'string') { 33 | throw new Errors.KeyAsScope(meta.line, parts.slice(0, i + 1).join(".")); 34 | } 35 | context = context[key] || (context[key] = {}); 36 | } 37 | return context; 38 | }; 39 | 40 | module.exports = TranslationHash; 41 | 42 | log.debug('Initialized ' + log.name); 43 | -------------------------------------------------------------------------------- /lib/i18n.js: -------------------------------------------------------------------------------- 1 | var log = require('./log')('i18nline:i18n'); 2 | 3 | var I18n = require('i18n-js'); 4 | var extend = require('./extensions/i18n_js'); 5 | 6 | module.exports = extend(I18n) 7 | 8 | log.debug('Initialized ' + log.name); 9 | -------------------------------------------------------------------------------- /lib/i18nline.js: -------------------------------------------------------------------------------- 1 | var log = require('./log')('i18nline'); 2 | var Utils = require('./utils'); 3 | 4 | var I18nline = { 5 | set: function (key, value, fn) { 6 | var prevValue = this.config[key]; 7 | this.config[key] = value; 8 | if (fn) { 9 | try { 10 | fn(); 11 | } 12 | finally { 13 | this.config[key] = prevValue; 14 | } 15 | } 16 | }, 17 | 18 | configure: function(config) { 19 | var arrOpts = ['patterns','ignorePatterns','directories','ignoreDirectories'] 20 | Object.keys(config) 21 | .filter(function(key){return key !== 'plugins'}) 22 | .forEach(function(key){ 23 | if ((typeof config[key] == 'string') && (arrOpts.indexOf(key) !== -1)) { 24 | this.set(key, config[key].split(',')); 25 | } else { 26 | this.set(key, config[key]) 27 | } 28 | }.bind(this)); 29 | if (config.plugins) this.configurePlugins(config.plugins); 30 | return this; 31 | }, 32 | 33 | configurePlugins: function(plugins) { 34 | this.processors = this.Commands.Check.processors; 35 | plugins.forEach(function(p){p(this)}.bind(this)); 36 | }, 37 | 38 | config: { 39 | basePath: '.', 40 | ignoreDirectories: ['node_modules', 'bower_components', '.git', 'dist', 'build'], 41 | patterns: ['**/*.js', '**/*.jsx'], 42 | ignorePatterns: [], 43 | out: 'src/i18n', 44 | inferredKeyFormat: 'underscored_crc32', 45 | underscoredKeyLength: 50, 46 | defaultLocale: 'en', 47 | } 48 | }; 49 | 50 | module.exports = I18nline; 51 | 52 | log.debug('Initialized ' + log.name); 53 | -------------------------------------------------------------------------------- /lib/load-config.js: -------------------------------------------------------------------------------- 1 | var log = require('./log')('i18nline:load-config'); 2 | 3 | var fs = require("fs"); 4 | var extend = require('extend'); 5 | var chalk = require("chalk"); 6 | var r = chalk.red, m = chalk.magenta; 7 | 8 | function loadConfig() { 9 | var config = {}, pkg; 10 | try { 11 | pkg = require('pkgcfg')(); 12 | } 13 | catch(e) { 14 | try { 15 | pkg = JSON.parse(fs.readFileSync('package.json').toString()); 16 | } 17 | catch(e) { 18 | log.error(r(log.name + ': ERROR: Could not load package.json' + e.message), e); 19 | } 20 | } 21 | 22 | if (pkg && pkg.i18n) { 23 | config = extend({}, config, pkg.i18n); 24 | log.debug(log.name + ': loaded config from package.json', pkg.i18n); 25 | } 26 | 27 | if (fs.existsSync(".i18nrc")) { 28 | try { 29 | var rc = JSON.parse(fs.readFileSync(".i18nrc").toString()) 30 | config = extend({}, config, rc); 31 | log.debug(log.name + ': loaded config from .i8nrc', rc); 32 | } catch (e) { 33 | log.error(r(log.name + ': ERROR: Could not load config from .i18nrc'), e); 34 | } 35 | } 36 | 37 | var deps = extend({}, pkg && pkg.devDependencies || {}, pkg && pkg.dependencies || {}); 38 | var autoPlugins = Object.keys(deps) 39 | .filter(function(key){return key.endsWith('-i18nline') || key.startsWith('i18nline-');}) 40 | .map(function(key){ 41 | try {require(key); return key;} 42 | catch(e){log.warn(log.name + m(' WARN: Failed loading plugin from dependency ' + key + '@' + deps[key] + ': ') + e.message); return null;} 43 | }) 44 | .filter(function(key){return key;}); 45 | config.plugins = (config.plugins || []).concat(autoPlugins); 46 | 47 | if (config.plugins && config.plugins.length) { 48 | for (var i=0,pluginName; pluginName=config.plugins[i]; i++) { 49 | if (typeof pluginName == 'string') { 50 | log.debug(log.name + ': loading plugin ' + pluginName); 51 | try { 52 | config.plugins[i] = require(pluginName); 53 | } catch(e) { 54 | log.error(r(log.name + ': ERROR: Unable to load plugin ' + pluginName), e); 55 | } 56 | } 57 | } 58 | } 59 | 60 | return config; 61 | }; 62 | 63 | module.exports = loadConfig; 64 | 65 | log.debug('Initialized ' + log.name); 66 | -------------------------------------------------------------------------------- /lib/log.js: -------------------------------------------------------------------------------- 1 | var ulog = require('ulog'); 2 | 3 | module.exports = function(name, silent) { 4 | if (silent) ulog.level = ulog.NONE; 5 | return ulog(name); 6 | } 7 | -------------------------------------------------------------------------------- /lib/main.js: -------------------------------------------------------------------------------- 1 | var log = require('./log')('i18nline:main'); 2 | 3 | var I18nline = require('./i18nline'); 4 | var CallHelpers = require('./call_helpers'); 5 | var Errors = require('./errors'); 6 | var TranslateCall = require('./extractors/translate_call'); 7 | var TranslationHash = require('./extractors/translation_hash'); 8 | var Commands = require('./commands'); 9 | var loadConfig = require('./load-config'); 10 | 11 | I18nline.CallHelpers = CallHelpers; 12 | I18nline.Errors = Errors; 13 | I18nline.TranslateCall = TranslateCall; 14 | I18nline.TranslationHash = TranslationHash; 15 | I18nline.Commands = Commands; 16 | 17 | module.exports = I18nline.configure(loadConfig()); 18 | 19 | log.debug('Initialized ' + log.name); 20 | -------------------------------------------------------------------------------- /lib/pluralize.js: -------------------------------------------------------------------------------- 1 | var log = require('./log')('i18nline:pluralize'); 2 | 3 | // ported pluralizations from active_support/inflections.rb 4 | // (except for cow -> kine, because nobody does that) 5 | var skip = ['equipment', 'information', 'rice', 'money', 'species', 'series', 'fish', 'sheep', 'jeans']; 6 | var patterns = [ 7 | [/person$/i, 'people'], 8 | [/man$/i, 'men'], 9 | [/child$/i, 'children'], 10 | [/sex$/i, 'sexes'], 11 | [/move$/i, 'moves'], 12 | [/(quiz)$/i, '$1zes'], 13 | [/^(ox)$/i, '$1en'], 14 | [/([m|l])ouse$/i, '$1ice'], 15 | [/(matr|vert|ind)(?:ix|ex)$/i, '$1ices'], 16 | [/(x|ch|ss|sh)$/i, '$1es'], 17 | [/([^aeiouy]|qu)y$/i, '$1ies'], 18 | [/(hive)$/i, '$1s'], 19 | [/(?:([^f])fe|([lr])f)$/i, '$1$2ves'], 20 | [/sis$/i, 'ses'], 21 | [/([ti])um$/i, '$1a'], 22 | [/(buffal|tomat)o$/i, '$1oes'], 23 | [/(bu)s$/i, '$1ses'], 24 | [/(alias|status)$/i, '$1es'], 25 | [/(octop|vir)us$/i, '$1i'], 26 | [/(ax|test)is$/i, '$1es'], 27 | [/s$/i, 's'] 28 | ]; 29 | 30 | var pluralize = function(string) { 31 | string = string || ''; 32 | if (skip.indexOf(string) >= 0) { 33 | return string; 34 | } 35 | for (var i = 0, len = patterns.length; i < len; i++) { 36 | var pair = patterns[i]; 37 | if (string.match(pair[0])) { 38 | return string.replace(pair[0], pair[1]); 39 | } 40 | } 41 | return string + "s"; 42 | }; 43 | 44 | pluralize.withCount = function(count, string) { 45 | return "" + count + " " + (count === 1 ? string : pluralize(string)); 46 | }; 47 | 48 | module.exports = pluralize; 49 | 50 | log.debug('Initialized ' + log.name); 51 | -------------------------------------------------------------------------------- /lib/processors/base_processor.js: -------------------------------------------------------------------------------- 1 | var log = require('../log')('i18nline:processors:base'); 2 | 3 | var fs = require('fs'); 4 | var path = require('path'); 5 | var Globby = require('gglobby'); 6 | var GlObject = require('gglobby/dist/lib/globject'); 7 | 8 | var Utils = require('../utils'); 9 | var I18nline = require('../i18nline'); 10 | 11 | function chdir(dir, cb) { 12 | var origDir = process.cwd(); 13 | try { 14 | process.chdir(dir); 15 | return cb(); 16 | } 17 | finally { 18 | process.chdir(origDir); 19 | } 20 | } 21 | 22 | function getFilesAndDirs(root, files, dirs) { 23 | root = root === "." ? "" : root + "/"; 24 | files = files || []; 25 | dirs = dirs || []; 26 | fs.readdirSync(root || ".").forEach(function(entry){ 27 | if (fs.statSync(root + entry).isDirectory()) { 28 | dirs.push(root + entry + "/"); 29 | getFilesAndDirs(root + entry, files, dirs); 30 | } else { 31 | files.push(root + entry); 32 | } 33 | }) 34 | return {files:files, dirs:dirs}; 35 | } 36 | 37 | function BaseProcessor(translations, options) { 38 | this.translations = translations; 39 | this.translationCount = 0; 40 | this.fileCount = 0; 41 | this.file = options.file; 42 | this.only = options.only; 43 | this.checkWrapper = options.checkWrapper || this.checkWrapper; 44 | this.directories = options.directories; 45 | this.patterns = options.patterns; 46 | this.ignorePatterns = options.ignorePatterns; 47 | if (I18nline.config.out && !I18nline.config.out.endsWith('.json')) { 48 | this.ignorePatterns.push(I18nline.config.out + (I18nline.config.out.endsWith('/') ? '':'/') + 'index.js'); 49 | } 50 | } 51 | 52 | BaseProcessor.prototype.checkWrapper = function(file, checker) { 53 | return checker(file); 54 | }; 55 | 56 | BaseProcessor.prototype.ignore = function() { 57 | if (fs.existsSync(".i18nignore")) { 58 | return fs.readFileSync(".i18nignore").toString().trim().split(/\r?\n|\r/) 59 | } 60 | return []; 61 | } 62 | 63 | BaseProcessor.prototype.files = function(directory) { 64 | var result = Globby 65 | .select(this.patterns, getFilesAndDirs(directory)) 66 | .reject(this.ignorePatterns) 67 | .reject(this.ignore()); 68 | if (this.only) { 69 | result = result.select(this.only instanceof Array ? this.only : [this.only]); 70 | } 71 | return result.files; 72 | }; 73 | 74 | BaseProcessor.prototype.checkFiles = function() { 75 | var directories = this.getDirectories(); 76 | chdir(I18nline.config.basePath, function(){ 77 | for (var i=0,l=directories.length; i { 7 | // we use a switch here so the import statements are statically 8 | // analyzable. the use of import() will make build tools generate 9 | // separate bundles which are downloaded on-demand. 10 | switch (locale) { 11 | /*[I18N_IMPORT_PLACEHOLDER]*/ 12 | default: return import(/* webpackChunkName: 'i18n.default' */ './default.json'); 13 | } 14 | }; 15 | 16 | if ((typeof module === 'object') && module.hot) { 17 | /*[I18N_RELOAD_PLACEHOLDER]*/ 18 | module.hot.accept('./default.json', I18n.reload('default')); 19 | } 20 | 21 | export default I18n; 22 | -------------------------------------------------------------------------------- /lib/utils.js: -------------------------------------------------------------------------------- 1 | var log = require('./log')('i18nline:utils'); 2 | 3 | var htmlEntities = { 4 | "'": "'", 5 | "&": "&", 6 | '"': """, 7 | ">": ">", 8 | "<": "<" 9 | }; 10 | 11 | function HtmlSafeString(string) { 12 | this.string = (typeof string === 'string' ? string : "" + string); 13 | } 14 | HtmlSafeString.prototype.toString = function() { 15 | return this.string; 16 | }; 17 | 18 | var Utils = { 19 | HtmlSafeString: HtmlSafeString, 20 | 21 | difference: function(a1, a2) { 22 | var result = []; 23 | for (var i = 0, len = a1.length; i < len; i++) { 24 | if (a2.indexOf(a1[i]) === -1) 25 | result.push(a1[i]); 26 | } 27 | return result; 28 | }, 29 | 30 | keys: function(object) { 31 | var keys = []; 32 | for (var key in object) { 33 | if (object.hasOwnProperty(key)) 34 | keys.push(key); 35 | } 36 | return keys; 37 | }, 38 | 39 | htmlEscape: function(string) { 40 | if (typeof string === 'undefined' || string === null) return ''; 41 | if (string instanceof Utils.HtmlSafeString) return string.toString(); 42 | return String(string).replace(/[&<>"']/g, function(m){ return htmlEntities[m]; }); 43 | }, 44 | 45 | regexpEscape: function(string) { 46 | if (typeof string === 'undefined' || string === null) return ''; 47 | return String(string).replace(/([.*+?^=!:${}()|\[\]\/\\])/g, "\\$1"); 48 | }, 49 | 50 | extend: function() { 51 | var args = [].slice.call(arguments); 52 | var target = args.shift(); 53 | for (var i = 0, len = args.length; i < len; i++) { 54 | var source = args[i]; 55 | for (var key in source) { 56 | if (source.hasOwnProperty(key)) 57 | target[key] = source[key]; 58 | } 59 | } 60 | return target; 61 | }, 62 | }; 63 | 64 | module.exports = Utils; 65 | 66 | log.debug('Initialized ' + log.name); 67 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "i18nline", 3 | "version": "2.0.1", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "@sinonjs/formatio": { 8 | "version": "2.0.0", 9 | "resolved": "http://registry.npmjs.org/@sinonjs/formatio/-/formatio-2.0.0.tgz", 10 | "integrity": "sha512-ls6CAMA6/5gG+O/IdsBcblvnd8qcO/l1TYoNeAzp3wcISOxlPXQEus0mLcdwazEkWjaBdaJ3TaxmNgCLWwvWzg==", 11 | "dev": true, 12 | "requires": { 13 | "samsam": "1.3.0" 14 | } 15 | }, 16 | "ansi-regex": { 17 | "version": "2.1.1", 18 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", 19 | "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" 20 | }, 21 | "ansi-styles": { 22 | "version": "3.2.1", 23 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", 24 | "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", 25 | "requires": { 26 | "color-convert": "1.9.1" 27 | } 28 | }, 29 | "arr-diff": { 30 | "version": "4.0.0", 31 | "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", 32 | "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=", 33 | "dev": true 34 | }, 35 | "arr-flatten": { 36 | "version": "1.1.0", 37 | "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz", 38 | "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==", 39 | "dev": true 40 | }, 41 | "arr-union": { 42 | "version": "3.1.0", 43 | "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", 44 | "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=", 45 | "dev": true 46 | }, 47 | "array-unique": { 48 | "version": "0.3.2", 49 | "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", 50 | "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=", 51 | "dev": true 52 | }, 53 | "assertion-error": { 54 | "version": "1.1.0", 55 | "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", 56 | "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", 57 | "dev": true 58 | }, 59 | "assign-symbols": { 60 | "version": "1.0.0", 61 | "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", 62 | "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=", 63 | "dev": true 64 | }, 65 | "atob": { 66 | "version": "2.0.3", 67 | "resolved": "https://registry.npmjs.org/atob/-/atob-2.0.3.tgz", 68 | "integrity": "sha1-GcenYEc3dEaPILLS0DNyrX1Mv10=", 69 | "dev": true 70 | }, 71 | "babel-code-frame": { 72 | "version": "6.26.0", 73 | "resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.26.0.tgz", 74 | "integrity": "sha1-Y/1D99weO7fONZR9uP42mj9Yx0s=", 75 | "requires": { 76 | "chalk": "1.1.3", 77 | "esutils": "2.0.2", 78 | "js-tokens": "3.0.2" 79 | }, 80 | "dependencies": { 81 | "ansi-styles": { 82 | "version": "2.2.1", 83 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", 84 | "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=" 85 | }, 86 | "chalk": { 87 | "version": "1.1.3", 88 | "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", 89 | "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", 90 | "requires": { 91 | "ansi-styles": "2.2.1", 92 | "escape-string-regexp": "1.0.5", 93 | "has-ansi": "2.0.0", 94 | "strip-ansi": "3.0.1", 95 | "supports-color": "2.0.0" 96 | } 97 | }, 98 | "supports-color": { 99 | "version": "2.0.0", 100 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", 101 | "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=" 102 | } 103 | } 104 | }, 105 | "babel-messages": { 106 | "version": "6.23.0", 107 | "resolved": "https://registry.npmjs.org/babel-messages/-/babel-messages-6.23.0.tgz", 108 | "integrity": "sha1-8830cDhYA1sqKVHG7F7fbGLyYw4=", 109 | "requires": { 110 | "babel-runtime": "6.26.0" 111 | } 112 | }, 113 | "babel-runtime": { 114 | "version": "6.26.0", 115 | "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", 116 | "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", 117 | "requires": { 118 | "core-js": "2.5.3", 119 | "regenerator-runtime": "0.11.1" 120 | } 121 | }, 122 | "babel-traverse": { 123 | "version": "6.26.0", 124 | "resolved": "https://registry.npmjs.org/babel-traverse/-/babel-traverse-6.26.0.tgz", 125 | "integrity": "sha1-RqnL1+3MYsjlwGTi0tjQ9ANXZu4=", 126 | "requires": { 127 | "babel-code-frame": "6.26.0", 128 | "babel-messages": "6.23.0", 129 | "babel-runtime": "6.26.0", 130 | "babel-types": "6.26.0", 131 | "babylon": "6.18.0", 132 | "debug": "2.6.9", 133 | "globals": "9.18.0", 134 | "invariant": "2.2.4", 135 | "lodash": "4.17.5" 136 | } 137 | }, 138 | "babel-types": { 139 | "version": "6.26.0", 140 | "resolved": "https://registry.npmjs.org/babel-types/-/babel-types-6.26.0.tgz", 141 | "integrity": "sha1-o7Bz+Uq0nrb6Vc1lInozQ4BjJJc=", 142 | "requires": { 143 | "babel-runtime": "6.26.0", 144 | "esutils": "2.0.2", 145 | "lodash": "4.17.5", 146 | "to-fast-properties": "1.0.3" 147 | } 148 | }, 149 | "babylon": { 150 | "version": "6.18.0", 151 | "resolved": "https://registry.npmjs.org/babylon/-/babylon-6.18.0.tgz", 152 | "integrity": "sha512-q/UEjfGJ2Cm3oKV71DJz9d25TPnq5rhBVL2Q4fA5wcC3jcrdn7+SssEybFIxwAvvP+YCsCYNKughoF33GxgycQ==" 153 | }, 154 | "balanced-match": { 155 | "version": "1.0.0", 156 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", 157 | "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", 158 | "dev": true 159 | }, 160 | "base": { 161 | "version": "0.11.2", 162 | "resolved": "https://registry.npmjs.org/base/-/base-0.11.2.tgz", 163 | "integrity": "sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==", 164 | "dev": true, 165 | "requires": { 166 | "cache-base": "1.0.1", 167 | "class-utils": "0.3.6", 168 | "component-emitter": "1.2.1", 169 | "define-property": "1.0.0", 170 | "isobject": "3.0.1", 171 | "mixin-deep": "1.3.1", 172 | "pascalcase": "0.1.1" 173 | }, 174 | "dependencies": { 175 | "define-property": { 176 | "version": "1.0.0", 177 | "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", 178 | "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", 179 | "dev": true, 180 | "requires": { 181 | "is-descriptor": "1.0.2" 182 | } 183 | } 184 | } 185 | }, 186 | "brace-expansion": { 187 | "version": "1.1.11", 188 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", 189 | "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", 190 | "dev": true, 191 | "requires": { 192 | "balanced-match": "1.0.0", 193 | "concat-map": "0.0.1" 194 | } 195 | }, 196 | "braces": { 197 | "version": "2.3.1", 198 | "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.1.tgz", 199 | "integrity": "sha512-SO5lYHA3vO6gz66erVvedSCkp7AKWdv6VcQ2N4ysXfPxdAlxAMMAdwegGGcv1Bqwm7naF1hNdk5d6AAIEHV2nQ==", 200 | "dev": true, 201 | "requires": { 202 | "arr-flatten": "1.1.0", 203 | "array-unique": "0.3.2", 204 | "define-property": "1.0.0", 205 | "extend-shallow": "2.0.1", 206 | "fill-range": "4.0.0", 207 | "isobject": "3.0.1", 208 | "kind-of": "6.0.2", 209 | "repeat-element": "1.1.2", 210 | "snapdragon": "0.8.2", 211 | "snapdragon-node": "2.1.1", 212 | "split-string": "3.1.0", 213 | "to-regex": "3.0.2" 214 | }, 215 | "dependencies": { 216 | "define-property": { 217 | "version": "1.0.0", 218 | "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", 219 | "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", 220 | "dev": true, 221 | "requires": { 222 | "is-descriptor": "1.0.2" 223 | } 224 | }, 225 | "extend-shallow": { 226 | "version": "2.0.1", 227 | "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", 228 | "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", 229 | "dev": true, 230 | "requires": { 231 | "is-extendable": "0.1.1" 232 | } 233 | } 234 | } 235 | }, 236 | "browser-stdout": { 237 | "version": "1.3.1", 238 | "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", 239 | "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", 240 | "dev": true 241 | }, 242 | "cache-base": { 243 | "version": "1.0.1", 244 | "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz", 245 | "integrity": "sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==", 246 | "dev": true, 247 | "requires": { 248 | "collection-visit": "1.0.0", 249 | "component-emitter": "1.2.1", 250 | "get-value": "2.0.6", 251 | "has-value": "1.0.0", 252 | "isobject": "3.0.1", 253 | "set-value": "2.0.0", 254 | "to-object-path": "0.3.0", 255 | "union-value": "1.0.0", 256 | "unset-value": "1.0.0" 257 | } 258 | }, 259 | "chai": { 260 | "version": "4.1.2", 261 | "resolved": "https://registry.npmjs.org/chai/-/chai-4.1.2.tgz", 262 | "integrity": "sha1-D2RYS6ZC8PKs4oBiefTwbKI61zw=", 263 | "dev": true, 264 | "requires": { 265 | "assertion-error": "1.1.0", 266 | "check-error": "1.0.2", 267 | "deep-eql": "3.0.1", 268 | "get-func-name": "2.0.0", 269 | "pathval": "1.1.0", 270 | "type-detect": "4.0.8" 271 | } 272 | }, 273 | "chalk": { 274 | "version": "2.3.2", 275 | "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.3.2.tgz", 276 | "integrity": "sha512-ZM4j2/ld/YZDc3Ma8PgN7gyAk+kHMMMyzLNryCPGhWrsfAuDVeuid5bpRFTDgMH9JBK2lA4dyyAkkZYF/WcqDQ==", 277 | "requires": { 278 | "ansi-styles": "3.2.1", 279 | "escape-string-regexp": "1.0.5", 280 | "supports-color": "5.3.0" 281 | } 282 | }, 283 | "check-error": { 284 | "version": "1.0.2", 285 | "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz", 286 | "integrity": "sha1-V00xLt2Iu13YkS6Sht1sCu1KrII=", 287 | "dev": true 288 | }, 289 | "class-utils": { 290 | "version": "0.3.6", 291 | "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz", 292 | "integrity": "sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==", 293 | "dev": true, 294 | "requires": { 295 | "arr-union": "3.1.0", 296 | "define-property": "0.2.5", 297 | "isobject": "3.0.1", 298 | "static-extend": "0.1.2" 299 | }, 300 | "dependencies": { 301 | "define-property": { 302 | "version": "0.2.5", 303 | "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", 304 | "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", 305 | "dev": true, 306 | "requires": { 307 | "is-descriptor": "0.1.6" 308 | } 309 | }, 310 | "is-accessor-descriptor": { 311 | "version": "0.1.6", 312 | "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", 313 | "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", 314 | "dev": true, 315 | "requires": { 316 | "kind-of": "3.2.2" 317 | }, 318 | "dependencies": { 319 | "kind-of": { 320 | "version": "3.2.2", 321 | "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", 322 | "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", 323 | "dev": true, 324 | "requires": { 325 | "is-buffer": "1.1.6" 326 | } 327 | } 328 | } 329 | }, 330 | "is-data-descriptor": { 331 | "version": "0.1.4", 332 | "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", 333 | "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", 334 | "dev": true, 335 | "requires": { 336 | "kind-of": "3.2.2" 337 | }, 338 | "dependencies": { 339 | "kind-of": { 340 | "version": "3.2.2", 341 | "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", 342 | "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", 343 | "dev": true, 344 | "requires": { 345 | "is-buffer": "1.1.6" 346 | } 347 | } 348 | } 349 | }, 350 | "is-descriptor": { 351 | "version": "0.1.6", 352 | "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", 353 | "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", 354 | "dev": true, 355 | "requires": { 356 | "is-accessor-descriptor": "0.1.6", 357 | "is-data-descriptor": "0.1.4", 358 | "kind-of": "5.1.0" 359 | } 360 | }, 361 | "kind-of": { 362 | "version": "5.1.0", 363 | "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", 364 | "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", 365 | "dev": true 366 | } 367 | } 368 | }, 369 | "collection-visit": { 370 | "version": "1.0.0", 371 | "resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz", 372 | "integrity": "sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA=", 373 | "dev": true, 374 | "requires": { 375 | "map-visit": "1.0.0", 376 | "object-visit": "1.0.1" 377 | } 378 | }, 379 | "color-convert": { 380 | "version": "1.9.1", 381 | "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.1.tgz", 382 | "integrity": "sha512-mjGanIiwQJskCC18rPR6OmrZ6fm2Lc7PeGFYwCmy5J34wC6F1PzdGL6xeMfmgicfYcNLGuVFA3WzXtIDCQSZxQ==", 383 | "requires": { 384 | "color-name": "1.1.3" 385 | } 386 | }, 387 | "color-name": { 388 | "version": "1.1.3", 389 | "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", 390 | "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" 391 | }, 392 | "commander": { 393 | "version": "2.11.0", 394 | "resolved": "https://registry.npmjs.org/commander/-/commander-2.11.0.tgz", 395 | "integrity": "sha512-b0553uYA5YAEGgyYIGYROzKQ7X5RAqedkfjiZxwi0kL1g3bOaBNNZfYkzt/CL0umgD5wc9Jec2FbB98CjkMRvQ==", 396 | "dev": true 397 | }, 398 | "component-emitter": { 399 | "version": "1.2.1", 400 | "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz", 401 | "integrity": "sha1-E3kY1teCg/ffemt8WmPhQOaUJeY=", 402 | "dev": true 403 | }, 404 | "concat-map": { 405 | "version": "0.0.1", 406 | "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", 407 | "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", 408 | "dev": true 409 | }, 410 | "copy-descriptor": { 411 | "version": "0.1.1", 412 | "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz", 413 | "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=", 414 | "dev": true 415 | }, 416 | "core-js": { 417 | "version": "2.5.3", 418 | "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.5.3.tgz", 419 | "integrity": "sha1-isw4NFgk8W2DZbfJtCWRaOjtYD4=" 420 | }, 421 | "crc32": { 422 | "version": "0.2.2", 423 | "resolved": "https://registry.npmjs.org/crc32/-/crc32-0.2.2.tgz", 424 | "integrity": "sha1-etIg1v/c0Rn5/BJ6d3LKzqOQpLo=" 425 | }, 426 | "debug": { 427 | "version": "2.6.9", 428 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", 429 | "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", 430 | "requires": { 431 | "ms": "2.0.0" 432 | } 433 | }, 434 | "decode-uri-component": { 435 | "version": "0.2.0", 436 | "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz", 437 | "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=", 438 | "dev": true 439 | }, 440 | "deep-eql": { 441 | "version": "3.0.1", 442 | "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-3.0.1.tgz", 443 | "integrity": "sha512-+QeIQyN5ZuO+3Uk5DYh6/1eKO0m0YmJFGNmFHGACpf1ClL1nmlV/p4gNgbl2pJGxgXb4faqo6UE+M5ACEMyVcw==", 444 | "dev": true, 445 | "requires": { 446 | "type-detect": "4.0.8" 447 | } 448 | }, 449 | "define-property": { 450 | "version": "2.0.2", 451 | "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz", 452 | "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==", 453 | "dev": true, 454 | "requires": { 455 | "is-descriptor": "1.0.2", 456 | "isobject": "3.0.1" 457 | } 458 | }, 459 | "detect-file": { 460 | "version": "1.0.0", 461 | "resolved": "https://registry.npmjs.org/detect-file/-/detect-file-1.0.0.tgz", 462 | "integrity": "sha1-8NZtA2cqglyxtzvbP+YjEMjlUrc=", 463 | "dev": true 464 | }, 465 | "diff": { 466 | "version": "3.5.0", 467 | "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", 468 | "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", 469 | "dev": true 470 | }, 471 | "escape-string-regexp": { 472 | "version": "1.0.5", 473 | "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", 474 | "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" 475 | }, 476 | "esprima": { 477 | "version": "4.0.0", 478 | "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.0.tgz", 479 | "integrity": "sha512-oftTcaMu/EGrEIu904mWteKIv8vMuOgGYo7EhVJJN00R/EED9DCua/xxHRdYnKtcECzVg7xOWhflvJMnqcFZjw==" 480 | }, 481 | "esutils": { 482 | "version": "2.0.2", 483 | "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz", 484 | "integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=" 485 | }, 486 | "expand-brackets": { 487 | "version": "2.1.4", 488 | "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", 489 | "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=", 490 | "dev": true, 491 | "requires": { 492 | "debug": "2.6.9", 493 | "define-property": "0.2.5", 494 | "extend-shallow": "2.0.1", 495 | "posix-character-classes": "0.1.1", 496 | "regex-not": "1.0.2", 497 | "snapdragon": "0.8.2", 498 | "to-regex": "3.0.2" 499 | }, 500 | "dependencies": { 501 | "define-property": { 502 | "version": "0.2.5", 503 | "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", 504 | "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", 505 | "dev": true, 506 | "requires": { 507 | "is-descriptor": "0.1.6" 508 | } 509 | }, 510 | "extend-shallow": { 511 | "version": "2.0.1", 512 | "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", 513 | "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", 514 | "dev": true, 515 | "requires": { 516 | "is-extendable": "0.1.1" 517 | } 518 | }, 519 | "is-accessor-descriptor": { 520 | "version": "0.1.6", 521 | "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", 522 | "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", 523 | "dev": true, 524 | "requires": { 525 | "kind-of": "3.2.2" 526 | }, 527 | "dependencies": { 528 | "kind-of": { 529 | "version": "3.2.2", 530 | "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", 531 | "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", 532 | "dev": true, 533 | "requires": { 534 | "is-buffer": "1.1.6" 535 | } 536 | } 537 | } 538 | }, 539 | "is-data-descriptor": { 540 | "version": "0.1.4", 541 | "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", 542 | "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", 543 | "dev": true, 544 | "requires": { 545 | "kind-of": "3.2.2" 546 | }, 547 | "dependencies": { 548 | "kind-of": { 549 | "version": "3.2.2", 550 | "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", 551 | "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", 552 | "dev": true, 553 | "requires": { 554 | "is-buffer": "1.1.6" 555 | } 556 | } 557 | } 558 | }, 559 | "is-descriptor": { 560 | "version": "0.1.6", 561 | "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", 562 | "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", 563 | "dev": true, 564 | "requires": { 565 | "is-accessor-descriptor": "0.1.6", 566 | "is-data-descriptor": "0.1.4", 567 | "kind-of": "5.1.0" 568 | } 569 | }, 570 | "kind-of": { 571 | "version": "5.1.0", 572 | "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", 573 | "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", 574 | "dev": true 575 | } 576 | } 577 | }, 578 | "expand-tilde": { 579 | "version": "2.0.2", 580 | "resolved": "https://registry.npmjs.org/expand-tilde/-/expand-tilde-2.0.2.tgz", 581 | "integrity": "sha1-l+gBqgUt8CRU3kawK/YhZCzchQI=", 582 | "dev": true, 583 | "requires": { 584 | "homedir-polyfill": "1.0.1" 585 | } 586 | }, 587 | "extend": { 588 | "version": "3.0.1", 589 | "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.1.tgz", 590 | "integrity": "sha1-p1Xqe8Gt/MWjHOfnYtuq3F5jZEQ=" 591 | }, 592 | "extend-shallow": { 593 | "version": "3.0.2", 594 | "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", 595 | "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", 596 | "dev": true, 597 | "requires": { 598 | "assign-symbols": "1.0.0", 599 | "is-extendable": "1.0.1" 600 | }, 601 | "dependencies": { 602 | "is-extendable": { 603 | "version": "1.0.1", 604 | "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", 605 | "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", 606 | "dev": true, 607 | "requires": { 608 | "is-plain-object": "2.0.4" 609 | } 610 | } 611 | } 612 | }, 613 | "extglob": { 614 | "version": "2.0.4", 615 | "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", 616 | "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", 617 | "dev": true, 618 | "requires": { 619 | "array-unique": "0.3.2", 620 | "define-property": "1.0.0", 621 | "expand-brackets": "2.1.4", 622 | "extend-shallow": "2.0.1", 623 | "fragment-cache": "0.2.1", 624 | "regex-not": "1.0.2", 625 | "snapdragon": "0.8.2", 626 | "to-regex": "3.0.2" 627 | }, 628 | "dependencies": { 629 | "define-property": { 630 | "version": "1.0.0", 631 | "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", 632 | "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", 633 | "dev": true, 634 | "requires": { 635 | "is-descriptor": "1.0.2" 636 | } 637 | }, 638 | "extend-shallow": { 639 | "version": "2.0.1", 640 | "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", 641 | "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", 642 | "dev": true, 643 | "requires": { 644 | "is-extendable": "0.1.1" 645 | } 646 | } 647 | } 648 | }, 649 | "fill-range": { 650 | "version": "4.0.0", 651 | "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", 652 | "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", 653 | "dev": true, 654 | "requires": { 655 | "extend-shallow": "2.0.1", 656 | "is-number": "3.0.0", 657 | "repeat-string": "1.6.1", 658 | "to-regex-range": "2.1.1" 659 | }, 660 | "dependencies": { 661 | "extend-shallow": { 662 | "version": "2.0.1", 663 | "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", 664 | "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", 665 | "dev": true, 666 | "requires": { 667 | "is-extendable": "0.1.1" 668 | } 669 | } 670 | } 671 | }, 672 | "findup-sync": { 673 | "version": "2.0.0", 674 | "resolved": "https://registry.npmjs.org/findup-sync/-/findup-sync-2.0.0.tgz", 675 | "integrity": "sha1-kyaxSIwi0aYIhlCoaQGy2akKLLw=", 676 | "dev": true, 677 | "requires": { 678 | "detect-file": "1.0.0", 679 | "is-glob": "3.1.0", 680 | "micromatch": "3.1.9", 681 | "resolve-dir": "1.0.1" 682 | } 683 | }, 684 | "for-in": { 685 | "version": "1.0.2", 686 | "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", 687 | "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=", 688 | "dev": true 689 | }, 690 | "fragment-cache": { 691 | "version": "0.2.1", 692 | "resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz", 693 | "integrity": "sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk=", 694 | "dev": true, 695 | "requires": { 696 | "map-cache": "0.2.2" 697 | } 698 | }, 699 | "fs.realpath": { 700 | "version": "1.0.0", 701 | "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", 702 | "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", 703 | "dev": true 704 | }, 705 | "get-func-name": { 706 | "version": "2.0.0", 707 | "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz", 708 | "integrity": "sha1-6td0q+5y4gQJQzoGY2YCPdaIekE=", 709 | "dev": true 710 | }, 711 | "get-value": { 712 | "version": "2.0.6", 713 | "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz", 714 | "integrity": "sha1-3BXKHGcjh8p2vTesCjlbogQqLCg=", 715 | "dev": true 716 | }, 717 | "gglobby": { 718 | "version": "0.0.3", 719 | "resolved": "https://registry.npmjs.org/gglobby/-/gglobby-0.0.3.tgz", 720 | "integrity": "sha1-x9aGwf8spOiCpYrfOKfeJpG6f5k=" 721 | }, 722 | "glob": { 723 | "version": "7.1.2", 724 | "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", 725 | "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", 726 | "dev": true, 727 | "requires": { 728 | "fs.realpath": "1.0.0", 729 | "inflight": "1.0.6", 730 | "inherits": "2.0.3", 731 | "minimatch": "3.0.4", 732 | "once": "1.4.0", 733 | "path-is-absolute": "1.0.1" 734 | } 735 | }, 736 | "global-modules": { 737 | "version": "1.0.0", 738 | "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-1.0.0.tgz", 739 | "integrity": "sha512-sKzpEkf11GpOFuw0Zzjzmt4B4UZwjOcG757PPvrfhxcLFbq0wpsgpOqxpxtxFiCG4DtG93M6XRVbF2oGdev7bg==", 740 | "dev": true, 741 | "requires": { 742 | "global-prefix": "1.0.2", 743 | "is-windows": "1.0.2", 744 | "resolve-dir": "1.0.1" 745 | } 746 | }, 747 | "global-prefix": { 748 | "version": "1.0.2", 749 | "resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-1.0.2.tgz", 750 | "integrity": "sha1-2/dDxsFJklk8ZVVoy2btMsASLr4=", 751 | "dev": true, 752 | "requires": { 753 | "expand-tilde": "2.0.2", 754 | "homedir-polyfill": "1.0.1", 755 | "ini": "1.3.5", 756 | "is-windows": "1.0.2", 757 | "which": "1.3.0" 758 | } 759 | }, 760 | "globals": { 761 | "version": "9.18.0", 762 | "resolved": "https://registry.npmjs.org/globals/-/globals-9.18.0.tgz", 763 | "integrity": "sha512-S0nG3CLEQiY/ILxqtztTWH/3iRRdyBLw6KMDxnKMchrtbj2OFmehVh0WUCfW3DUrIgx/qFrJPICrq4Z4sTR9UQ==" 764 | }, 765 | "growl": { 766 | "version": "1.10.3", 767 | "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.3.tgz", 768 | "integrity": "sha512-hKlsbA5Vu3xsh1Cg3J7jSmX/WaW6A5oBeqzM88oNbCRQFz+zUaXm6yxS4RVytp1scBoJzSYl4YAEOQIt6O8V1Q==", 769 | "dev": true 770 | }, 771 | "has-ansi": { 772 | "version": "2.0.0", 773 | "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", 774 | "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", 775 | "requires": { 776 | "ansi-regex": "2.1.1" 777 | } 778 | }, 779 | "has-flag": { 780 | "version": "2.0.0", 781 | "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-2.0.0.tgz", 782 | "integrity": "sha1-6CB68cx7MNRGzHC3NLXovhj4jVE=", 783 | "dev": true 784 | }, 785 | "has-value": { 786 | "version": "1.0.0", 787 | "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz", 788 | "integrity": "sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc=", 789 | "dev": true, 790 | "requires": { 791 | "get-value": "2.0.6", 792 | "has-values": "1.0.0", 793 | "isobject": "3.0.1" 794 | } 795 | }, 796 | "has-values": { 797 | "version": "1.0.0", 798 | "resolved": "https://registry.npmjs.org/has-values/-/has-values-1.0.0.tgz", 799 | "integrity": "sha1-lbC2P+whRmGab+V/51Yo1aOe/k8=", 800 | "dev": true, 801 | "requires": { 802 | "is-number": "3.0.0", 803 | "kind-of": "4.0.0" 804 | }, 805 | "dependencies": { 806 | "kind-of": { 807 | "version": "4.0.0", 808 | "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz", 809 | "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=", 810 | "dev": true, 811 | "requires": { 812 | "is-buffer": "1.1.6" 813 | } 814 | } 815 | } 816 | }, 817 | "he": { 818 | "version": "1.1.1", 819 | "resolved": "https://registry.npmjs.org/he/-/he-1.1.1.tgz", 820 | "integrity": "sha1-k0EP0hsAlzUVH4howvJx80J+I/0=", 821 | "dev": true 822 | }, 823 | "homedir-polyfill": { 824 | "version": "1.0.1", 825 | "resolved": "https://registry.npmjs.org/homedir-polyfill/-/homedir-polyfill-1.0.1.tgz", 826 | "integrity": "sha1-TCu8inWJmP7r9e1oWA921GdotLw=", 827 | "dev": true, 828 | "requires": { 829 | "parse-passwd": "1.0.0" 830 | } 831 | }, 832 | "i18n-js": { 833 | "version": "3.0.3", 834 | "resolved": "https://registry.npmjs.org/i18n-js/-/i18n-js-3.0.3.tgz", 835 | "integrity": "sha512-u144MQhV/8mz4Y5wP86SQAWMwS8gpe/JavIa9hugSI4WreezGgbhJPdk2Q60KcdIltKLiNefGtHNh1N8SSmQqQ==" 836 | }, 837 | "inflight": { 838 | "version": "1.0.6", 839 | "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", 840 | "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", 841 | "dev": true, 842 | "requires": { 843 | "once": "1.4.0", 844 | "wrappy": "1.0.2" 845 | } 846 | }, 847 | "inherits": { 848 | "version": "2.0.3", 849 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", 850 | "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", 851 | "dev": true 852 | }, 853 | "ini": { 854 | "version": "1.3.5", 855 | "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", 856 | "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==", 857 | "dev": true 858 | }, 859 | "invariant": { 860 | "version": "2.2.4", 861 | "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", 862 | "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", 863 | "requires": { 864 | "loose-envify": "1.3.1" 865 | } 866 | }, 867 | "is-accessor-descriptor": { 868 | "version": "1.0.0", 869 | "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", 870 | "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", 871 | "dev": true, 872 | "requires": { 873 | "kind-of": "6.0.2" 874 | } 875 | }, 876 | "is-buffer": { 877 | "version": "1.1.6", 878 | "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", 879 | "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", 880 | "dev": true 881 | }, 882 | "is-data-descriptor": { 883 | "version": "1.0.0", 884 | "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", 885 | "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", 886 | "dev": true, 887 | "requires": { 888 | "kind-of": "6.0.2" 889 | } 890 | }, 891 | "is-descriptor": { 892 | "version": "1.0.2", 893 | "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", 894 | "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", 895 | "dev": true, 896 | "requires": { 897 | "is-accessor-descriptor": "1.0.0", 898 | "is-data-descriptor": "1.0.0", 899 | "kind-of": "6.0.2" 900 | } 901 | }, 902 | "is-extendable": { 903 | "version": "0.1.1", 904 | "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", 905 | "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", 906 | "dev": true 907 | }, 908 | "is-extglob": { 909 | "version": "2.1.1", 910 | "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", 911 | "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", 912 | "dev": true 913 | }, 914 | "is-glob": { 915 | "version": "3.1.0", 916 | "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", 917 | "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", 918 | "dev": true, 919 | "requires": { 920 | "is-extglob": "2.1.1" 921 | } 922 | }, 923 | "is-number": { 924 | "version": "3.0.0", 925 | "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", 926 | "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", 927 | "dev": true, 928 | "requires": { 929 | "kind-of": "3.2.2" 930 | }, 931 | "dependencies": { 932 | "kind-of": { 933 | "version": "3.2.2", 934 | "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", 935 | "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", 936 | "dev": true, 937 | "requires": { 938 | "is-buffer": "1.1.6" 939 | } 940 | } 941 | } 942 | }, 943 | "is-odd": { 944 | "version": "2.0.0", 945 | "resolved": "https://registry.npmjs.org/is-odd/-/is-odd-2.0.0.tgz", 946 | "integrity": "sha512-OTiixgpZAT1M4NHgS5IguFp/Vz2VI3U7Goh4/HA1adtwyLtSBrxYlcSYkhpAE07s4fKEcjrFxyvtQBND4vFQyQ==", 947 | "dev": true, 948 | "requires": { 949 | "is-number": "4.0.0" 950 | }, 951 | "dependencies": { 952 | "is-number": { 953 | "version": "4.0.0", 954 | "resolved": "https://registry.npmjs.org/is-number/-/is-number-4.0.0.tgz", 955 | "integrity": "sha512-rSklcAIlf1OmFdyAqbnWTLVelsQ58uvZ66S/ZyawjWqIviTWCjg2PzVGw8WUA+nNuPTqb4wgA+NszrJ+08LlgQ==", 956 | "dev": true 957 | } 958 | } 959 | }, 960 | "is-plain-object": { 961 | "version": "2.0.4", 962 | "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", 963 | "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", 964 | "dev": true, 965 | "requires": { 966 | "isobject": "3.0.1" 967 | } 968 | }, 969 | "is-windows": { 970 | "version": "1.0.2", 971 | "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", 972 | "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", 973 | "dev": true 974 | }, 975 | "isarray": { 976 | "version": "1.0.0", 977 | "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", 978 | "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", 979 | "dev": true 980 | }, 981 | "isexe": { 982 | "version": "2.0.0", 983 | "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", 984 | "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", 985 | "dev": true 986 | }, 987 | "isobject": { 988 | "version": "3.0.1", 989 | "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", 990 | "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", 991 | "dev": true 992 | }, 993 | "js-tokens": { 994 | "version": "3.0.2", 995 | "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz", 996 | "integrity": "sha1-mGbfOVECEw449/mWvOtlRDIJwls=" 997 | }, 998 | "just-extend": { 999 | "version": "1.1.27", 1000 | "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-1.1.27.tgz", 1001 | "integrity": "sha512-mJVp13Ix6gFo3SBAy9U/kL+oeZqzlYYYLQBwXVBlVzIsZwBqGREnOro24oC/8s8aox+rJhtZ2DiQof++IrkA+g==", 1002 | "dev": true 1003 | }, 1004 | "kind-of": { 1005 | "version": "6.0.2", 1006 | "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", 1007 | "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", 1008 | "dev": true 1009 | }, 1010 | "lodash": { 1011 | "version": "4.17.5", 1012 | "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.5.tgz", 1013 | "integrity": "sha512-svL3uiZf1RwhH+cWrfZn3A4+U58wbP0tGVTLQPbjplZxZ8ROD9VLuNgsRniTlLe7OlSqR79RUehXgpBW/s0IQw==" 1014 | }, 1015 | "lodash.get": { 1016 | "version": "4.4.2", 1017 | "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", 1018 | "integrity": "sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk=", 1019 | "dev": true 1020 | }, 1021 | "lolex": { 1022 | "version": "2.3.2", 1023 | "resolved": "https://registry.npmjs.org/lolex/-/lolex-2.3.2.tgz", 1024 | "integrity": "sha512-A5pN2tkFj7H0dGIAM6MFvHKMJcPnjZsOMvR7ujCjfgW5TbV6H9vb1PgxLtHvjqNZTHsUolz+6/WEO0N1xNx2ng==", 1025 | "dev": true 1026 | }, 1027 | "loose-envify": { 1028 | "version": "1.3.1", 1029 | "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.3.1.tgz", 1030 | "integrity": "sha1-0aitM/qc4OcT1l/dCsi3SNR4yEg=", 1031 | "requires": { 1032 | "js-tokens": "3.0.2" 1033 | } 1034 | }, 1035 | "map-cache": { 1036 | "version": "0.2.2", 1037 | "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz", 1038 | "integrity": "sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8=", 1039 | "dev": true 1040 | }, 1041 | "map-visit": { 1042 | "version": "1.0.0", 1043 | "resolved": "https://registry.npmjs.org/map-visit/-/map-visit-1.0.0.tgz", 1044 | "integrity": "sha1-7Nyo8TFE5mDxtb1B8S80edmN+48=", 1045 | "dev": true, 1046 | "requires": { 1047 | "object-visit": "1.0.1" 1048 | } 1049 | }, 1050 | "matchdep": { 1051 | "version": "2.0.0", 1052 | "resolved": "https://registry.npmjs.org/matchdep/-/matchdep-2.0.0.tgz", 1053 | "integrity": "sha1-xvNINKDY28OzfCfui7yyfHd1WC4=", 1054 | "dev": true, 1055 | "requires": { 1056 | "findup-sync": "2.0.0", 1057 | "micromatch": "3.1.9", 1058 | "resolve": "1.5.0", 1059 | "stack-trace": "0.0.10" 1060 | } 1061 | }, 1062 | "micromatch": { 1063 | "version": "3.1.9", 1064 | "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.9.tgz", 1065 | "integrity": "sha512-SlIz6sv5UPaAVVFRKodKjCg48EbNoIhgetzfK/Cy0v5U52Z6zB136M8tp0UC9jM53LYbmIRihJszvvqpKkfm9g==", 1066 | "dev": true, 1067 | "requires": { 1068 | "arr-diff": "4.0.0", 1069 | "array-unique": "0.3.2", 1070 | "braces": "2.3.1", 1071 | "define-property": "2.0.2", 1072 | "extend-shallow": "3.0.2", 1073 | "extglob": "2.0.4", 1074 | "fragment-cache": "0.2.1", 1075 | "kind-of": "6.0.2", 1076 | "nanomatch": "1.2.9", 1077 | "object.pick": "1.3.0", 1078 | "regex-not": "1.0.2", 1079 | "snapdragon": "0.8.2", 1080 | "to-regex": "3.0.2" 1081 | } 1082 | }, 1083 | "minimatch": { 1084 | "version": "3.0.4", 1085 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", 1086 | "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", 1087 | "dev": true, 1088 | "requires": { 1089 | "brace-expansion": "1.1.11" 1090 | } 1091 | }, 1092 | "minimist": { 1093 | "version": "1.2.0", 1094 | "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", 1095 | "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=" 1096 | }, 1097 | "mixin-deep": { 1098 | "version": "1.3.1", 1099 | "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.1.tgz", 1100 | "integrity": "sha512-8ZItLHeEgaqEvd5lYBXfm4EZSFCX29Jb9K+lAHhDKzReKBQKj3R+7NOF6tjqYi9t4oI8VUfaWITJQm86wnXGNQ==", 1101 | "dev": true, 1102 | "requires": { 1103 | "for-in": "1.0.2", 1104 | "is-extendable": "1.0.1" 1105 | }, 1106 | "dependencies": { 1107 | "is-extendable": { 1108 | "version": "1.0.1", 1109 | "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", 1110 | "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", 1111 | "dev": true, 1112 | "requires": { 1113 | "is-plain-object": "2.0.4" 1114 | } 1115 | } 1116 | } 1117 | }, 1118 | "mkdirp": { 1119 | "version": "0.5.1", 1120 | "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", 1121 | "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", 1122 | "requires": { 1123 | "minimist": "0.0.8" 1124 | }, 1125 | "dependencies": { 1126 | "minimist": { 1127 | "version": "0.0.8", 1128 | "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", 1129 | "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=" 1130 | } 1131 | } 1132 | }, 1133 | "mocha": { 1134 | "version": "5.0.5", 1135 | "resolved": "https://registry.npmjs.org/mocha/-/mocha-5.0.5.tgz", 1136 | "integrity": "sha512-3MM3UjZ5p8EJrYpG7s+29HAI9G7sTzKEe4+w37Dg0QP7qL4XGsV+Q2xet2cE37AqdgN1OtYQB6Vl98YiPV3PgA==", 1137 | "dev": true, 1138 | "requires": { 1139 | "browser-stdout": "1.3.1", 1140 | "commander": "2.11.0", 1141 | "debug": "3.1.0", 1142 | "diff": "3.5.0", 1143 | "escape-string-regexp": "1.0.5", 1144 | "glob": "7.1.2", 1145 | "growl": "1.10.3", 1146 | "he": "1.1.1", 1147 | "mkdirp": "0.5.1", 1148 | "supports-color": "4.4.0" 1149 | }, 1150 | "dependencies": { 1151 | "debug": { 1152 | "version": "3.1.0", 1153 | "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", 1154 | "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", 1155 | "dev": true, 1156 | "requires": { 1157 | "ms": "2.0.0" 1158 | } 1159 | }, 1160 | "supports-color": { 1161 | "version": "4.4.0", 1162 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-4.4.0.tgz", 1163 | "integrity": "sha512-rKC3+DyXWgK0ZLKwmRsrkyHVZAjNkfzeehuFWdGGcqGDTZFH73+RH6S/RDAAxl9GusSjZSUWYLmT9N5pzXFOXQ==", 1164 | "dev": true, 1165 | "requires": { 1166 | "has-flag": "2.0.0" 1167 | } 1168 | } 1169 | } 1170 | }, 1171 | "ms": { 1172 | "version": "2.0.0", 1173 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", 1174 | "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" 1175 | }, 1176 | "nanomatch": { 1177 | "version": "1.2.9", 1178 | "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.9.tgz", 1179 | "integrity": "sha512-n8R9bS8yQ6eSXaV6jHUpKzD8gLsin02w1HSFiegwrs9E098Ylhw5jdyKPaYqvHknHaSCKTPp7C8dGCQ0q9koXA==", 1180 | "dev": true, 1181 | "requires": { 1182 | "arr-diff": "4.0.0", 1183 | "array-unique": "0.3.2", 1184 | "define-property": "2.0.2", 1185 | "extend-shallow": "3.0.2", 1186 | "fragment-cache": "0.2.1", 1187 | "is-odd": "2.0.0", 1188 | "is-windows": "1.0.2", 1189 | "kind-of": "6.0.2", 1190 | "object.pick": "1.3.0", 1191 | "regex-not": "1.0.2", 1192 | "snapdragon": "0.8.2", 1193 | "to-regex": "3.0.2" 1194 | } 1195 | }, 1196 | "nise": { 1197 | "version": "1.3.2", 1198 | "resolved": "https://registry.npmjs.org/nise/-/nise-1.3.2.tgz", 1199 | "integrity": "sha512-KPKb+wvETBiwb4eTwtR/OsA2+iijXP+VnlSFYJo3EHjm2yjek1NWxHOUQat3i7xNLm1Bm18UA5j5Wor0yO2GtA==", 1200 | "dev": true, 1201 | "requires": { 1202 | "@sinonjs/formatio": "2.0.0", 1203 | "just-extend": "1.1.27", 1204 | "lolex": "2.3.2", 1205 | "path-to-regexp": "1.7.0", 1206 | "text-encoding": "0.6.4" 1207 | } 1208 | }, 1209 | "object-copy": { 1210 | "version": "0.1.0", 1211 | "resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz", 1212 | "integrity": "sha1-fn2Fi3gb18mRpBupde04EnVOmYw=", 1213 | "dev": true, 1214 | "requires": { 1215 | "copy-descriptor": "0.1.1", 1216 | "define-property": "0.2.5", 1217 | "kind-of": "3.2.2" 1218 | }, 1219 | "dependencies": { 1220 | "define-property": { 1221 | "version": "0.2.5", 1222 | "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", 1223 | "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", 1224 | "dev": true, 1225 | "requires": { 1226 | "is-descriptor": "0.1.6" 1227 | } 1228 | }, 1229 | "is-accessor-descriptor": { 1230 | "version": "0.1.6", 1231 | "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", 1232 | "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", 1233 | "dev": true, 1234 | "requires": { 1235 | "kind-of": "3.2.2" 1236 | } 1237 | }, 1238 | "is-data-descriptor": { 1239 | "version": "0.1.4", 1240 | "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", 1241 | "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", 1242 | "dev": true, 1243 | "requires": { 1244 | "kind-of": "3.2.2" 1245 | } 1246 | }, 1247 | "is-descriptor": { 1248 | "version": "0.1.6", 1249 | "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", 1250 | "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", 1251 | "dev": true, 1252 | "requires": { 1253 | "is-accessor-descriptor": "0.1.6", 1254 | "is-data-descriptor": "0.1.4", 1255 | "kind-of": "5.1.0" 1256 | }, 1257 | "dependencies": { 1258 | "kind-of": { 1259 | "version": "5.1.0", 1260 | "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", 1261 | "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", 1262 | "dev": true 1263 | } 1264 | } 1265 | }, 1266 | "kind-of": { 1267 | "version": "3.2.2", 1268 | "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", 1269 | "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", 1270 | "dev": true, 1271 | "requires": { 1272 | "is-buffer": "1.1.6" 1273 | } 1274 | } 1275 | } 1276 | }, 1277 | "object-path": { 1278 | "version": "0.11.4", 1279 | "resolved": "https://registry.npmjs.org/object-path/-/object-path-0.11.4.tgz", 1280 | "integrity": "sha1-NwrnUvvzfePqcKhhwju6iRVpGUk=", 1281 | "dev": true 1282 | }, 1283 | "object-visit": { 1284 | "version": "1.0.1", 1285 | "resolved": "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz", 1286 | "integrity": "sha1-95xEk68MU3e1n+OdOV5BBC3QRbs=", 1287 | "dev": true, 1288 | "requires": { 1289 | "isobject": "3.0.1" 1290 | } 1291 | }, 1292 | "object.pick": { 1293 | "version": "1.3.0", 1294 | "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz", 1295 | "integrity": "sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c=", 1296 | "dev": true, 1297 | "requires": { 1298 | "isobject": "3.0.1" 1299 | } 1300 | }, 1301 | "once": { 1302 | "version": "1.4.0", 1303 | "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", 1304 | "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", 1305 | "dev": true, 1306 | "requires": { 1307 | "wrappy": "1.0.2" 1308 | } 1309 | }, 1310 | "os-tmpdir": { 1311 | "version": "1.0.2", 1312 | "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", 1313 | "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", 1314 | "dev": true 1315 | }, 1316 | "parse-passwd": { 1317 | "version": "1.0.0", 1318 | "resolved": "https://registry.npmjs.org/parse-passwd/-/parse-passwd-1.0.0.tgz", 1319 | "integrity": "sha1-bVuTSkVpk7I9N/QKOC1vFmao5cY=", 1320 | "dev": true 1321 | }, 1322 | "pascalcase": { 1323 | "version": "0.1.1", 1324 | "resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz", 1325 | "integrity": "sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ=", 1326 | "dev": true 1327 | }, 1328 | "path-is-absolute": { 1329 | "version": "1.0.1", 1330 | "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", 1331 | "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", 1332 | "dev": true 1333 | }, 1334 | "path-parse": { 1335 | "version": "1.0.5", 1336 | "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.5.tgz", 1337 | "integrity": "sha1-PBrfhx6pzWyUMbbqK9dKD/BVxME=", 1338 | "dev": true 1339 | }, 1340 | "path-to-regexp": { 1341 | "version": "1.7.0", 1342 | "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.7.0.tgz", 1343 | "integrity": "sha1-Wf3g9DW62suhA6hOnTvGTpa5k30=", 1344 | "dev": true, 1345 | "requires": { 1346 | "isarray": "0.0.1" 1347 | }, 1348 | "dependencies": { 1349 | "isarray": { 1350 | "version": "0.0.1", 1351 | "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", 1352 | "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", 1353 | "dev": true 1354 | } 1355 | } 1356 | }, 1357 | "pathval": { 1358 | "version": "1.1.0", 1359 | "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.0.tgz", 1360 | "integrity": "sha1-uULm1L3mUwBe9rcTYd74cn0GReA=", 1361 | "dev": true 1362 | }, 1363 | "pkgcfg": { 1364 | "version": "0.9.1", 1365 | "resolved": "https://registry.npmjs.org/pkgcfg/-/pkgcfg-0.9.1.tgz", 1366 | "integrity": "sha512-s3O4qqI5xfRjy3DCmbqoPynXKARJA4gE9EU4QDqCO6USS6EhCZ+JmfNKV65KqPl7GvRkoGZJdlbfVeYxxq8r3g==", 1367 | "dev": true, 1368 | "requires": { 1369 | "object-path": "0.11.4", 1370 | "ulog": "1.1.0" 1371 | } 1372 | }, 1373 | "posix-character-classes": { 1374 | "version": "0.1.1", 1375 | "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz", 1376 | "integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=", 1377 | "dev": true 1378 | }, 1379 | "regenerator-runtime": { 1380 | "version": "0.11.1", 1381 | "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz", 1382 | "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==" 1383 | }, 1384 | "regex-not": { 1385 | "version": "1.0.2", 1386 | "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz", 1387 | "integrity": "sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==", 1388 | "dev": true, 1389 | "requires": { 1390 | "extend-shallow": "3.0.2", 1391 | "safe-regex": "1.1.0" 1392 | } 1393 | }, 1394 | "repeat-element": { 1395 | "version": "1.1.2", 1396 | "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.2.tgz", 1397 | "integrity": "sha1-7wiaF40Ug7quTZPrmLT55OEdmQo=", 1398 | "dev": true 1399 | }, 1400 | "repeat-string": { 1401 | "version": "1.6.1", 1402 | "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", 1403 | "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=", 1404 | "dev": true 1405 | }, 1406 | "resolve": { 1407 | "version": "1.5.0", 1408 | "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.5.0.tgz", 1409 | "integrity": "sha512-hgoSGrc3pjzAPHNBg+KnFcK2HwlHTs/YrAGUr6qgTVUZmXv1UEXXl0bZNBKMA9fud6lRYFdPGz0xXxycPzmmiw==", 1410 | "dev": true, 1411 | "requires": { 1412 | "path-parse": "1.0.5" 1413 | } 1414 | }, 1415 | "resolve-dir": { 1416 | "version": "1.0.1", 1417 | "resolved": "https://registry.npmjs.org/resolve-dir/-/resolve-dir-1.0.1.tgz", 1418 | "integrity": "sha1-eaQGRMNivoLybv/nOcm7U4IEb0M=", 1419 | "dev": true, 1420 | "requires": { 1421 | "expand-tilde": "2.0.2", 1422 | "global-modules": "1.0.0" 1423 | } 1424 | }, 1425 | "resolve-url": { 1426 | "version": "0.2.1", 1427 | "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", 1428 | "integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=", 1429 | "dev": true 1430 | }, 1431 | "ret": { 1432 | "version": "0.1.15", 1433 | "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz", 1434 | "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==", 1435 | "dev": true 1436 | }, 1437 | "rimraf": { 1438 | "version": "2.6.2", 1439 | "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.2.tgz", 1440 | "integrity": "sha512-lreewLK/BlghmxtfH36YYVg1i8IAce4TI7oao75I1g245+6BctqTVQiBP3YUJ9C6DQOXJmkYR9X9fCLtCOJc5w==", 1441 | "dev": true, 1442 | "requires": { 1443 | "glob": "7.1.2" 1444 | } 1445 | }, 1446 | "safe-regex": { 1447 | "version": "1.1.0", 1448 | "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz", 1449 | "integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=", 1450 | "dev": true, 1451 | "requires": { 1452 | "ret": "0.1.15" 1453 | } 1454 | }, 1455 | "samsam": { 1456 | "version": "1.3.0", 1457 | "resolved": "https://registry.npmjs.org/samsam/-/samsam-1.3.0.tgz", 1458 | "integrity": "sha512-1HwIYD/8UlOtFS3QO3w7ey+SdSDFE4HRNLZoZRYVQefrOY3l17epswImeB1ijgJFQJodIaHcwkp3r/myBjFVbg==", 1459 | "dev": true 1460 | }, 1461 | "set-value": { 1462 | "version": "2.0.0", 1463 | "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.0.tgz", 1464 | "integrity": "sha512-hw0yxk9GT/Hr5yJEYnHNKYXkIA8mVJgd9ditYZCe16ZczcaELYYcfvaXesNACk2O8O0nTiPQcQhGUQj8JLzeeg==", 1465 | "dev": true, 1466 | "requires": { 1467 | "extend-shallow": "2.0.1", 1468 | "is-extendable": "0.1.1", 1469 | "is-plain-object": "2.0.4", 1470 | "split-string": "3.1.0" 1471 | }, 1472 | "dependencies": { 1473 | "extend-shallow": { 1474 | "version": "2.0.1", 1475 | "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", 1476 | "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", 1477 | "dev": true, 1478 | "requires": { 1479 | "is-extendable": "0.1.1" 1480 | } 1481 | } 1482 | } 1483 | }, 1484 | "sinon": { 1485 | "version": "4.4.8", 1486 | "resolved": "https://registry.npmjs.org/sinon/-/sinon-4.4.8.tgz", 1487 | "integrity": "sha512-EWZf/D5BN/BbDFPmwY2abw6wgELVmk361self+lcwEmVw0WWUxURp2S/YoDB2WG/xurFVzKQglMARweYRWM6Hw==", 1488 | "dev": true, 1489 | "requires": { 1490 | "@sinonjs/formatio": "2.0.0", 1491 | "diff": "3.5.0", 1492 | "lodash.get": "4.4.2", 1493 | "lolex": "2.3.2", 1494 | "nise": "1.3.2", 1495 | "supports-color": "5.3.0", 1496 | "type-detect": "4.0.8" 1497 | } 1498 | }, 1499 | "snapdragon": { 1500 | "version": "0.8.2", 1501 | "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz", 1502 | "integrity": "sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==", 1503 | "dev": true, 1504 | "requires": { 1505 | "base": "0.11.2", 1506 | "debug": "2.6.9", 1507 | "define-property": "0.2.5", 1508 | "extend-shallow": "2.0.1", 1509 | "map-cache": "0.2.2", 1510 | "source-map": "0.5.6", 1511 | "source-map-resolve": "0.5.1", 1512 | "use": "3.1.0" 1513 | }, 1514 | "dependencies": { 1515 | "define-property": { 1516 | "version": "0.2.5", 1517 | "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", 1518 | "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", 1519 | "dev": true, 1520 | "requires": { 1521 | "is-descriptor": "0.1.6" 1522 | } 1523 | }, 1524 | "extend-shallow": { 1525 | "version": "2.0.1", 1526 | "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", 1527 | "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", 1528 | "dev": true, 1529 | "requires": { 1530 | "is-extendable": "0.1.1" 1531 | } 1532 | }, 1533 | "is-accessor-descriptor": { 1534 | "version": "0.1.6", 1535 | "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", 1536 | "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", 1537 | "dev": true, 1538 | "requires": { 1539 | "kind-of": "3.2.2" 1540 | }, 1541 | "dependencies": { 1542 | "kind-of": { 1543 | "version": "3.2.2", 1544 | "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", 1545 | "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", 1546 | "dev": true, 1547 | "requires": { 1548 | "is-buffer": "1.1.6" 1549 | } 1550 | } 1551 | } 1552 | }, 1553 | "is-data-descriptor": { 1554 | "version": "0.1.4", 1555 | "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", 1556 | "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", 1557 | "dev": true, 1558 | "requires": { 1559 | "kind-of": "3.2.2" 1560 | }, 1561 | "dependencies": { 1562 | "kind-of": { 1563 | "version": "3.2.2", 1564 | "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", 1565 | "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", 1566 | "dev": true, 1567 | "requires": { 1568 | "is-buffer": "1.1.6" 1569 | } 1570 | } 1571 | } 1572 | }, 1573 | "is-descriptor": { 1574 | "version": "0.1.6", 1575 | "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", 1576 | "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", 1577 | "dev": true, 1578 | "requires": { 1579 | "is-accessor-descriptor": "0.1.6", 1580 | "is-data-descriptor": "0.1.4", 1581 | "kind-of": "5.1.0" 1582 | } 1583 | }, 1584 | "kind-of": { 1585 | "version": "5.1.0", 1586 | "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", 1587 | "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", 1588 | "dev": true 1589 | } 1590 | } 1591 | }, 1592 | "snapdragon-node": { 1593 | "version": "2.1.1", 1594 | "resolved": "https://registry.npmjs.org/snapdragon-node/-/snapdragon-node-2.1.1.tgz", 1595 | "integrity": "sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==", 1596 | "dev": true, 1597 | "requires": { 1598 | "define-property": "1.0.0", 1599 | "isobject": "3.0.1", 1600 | "snapdragon-util": "3.0.1" 1601 | }, 1602 | "dependencies": { 1603 | "define-property": { 1604 | "version": "1.0.0", 1605 | "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", 1606 | "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", 1607 | "dev": true, 1608 | "requires": { 1609 | "is-descriptor": "1.0.2" 1610 | } 1611 | } 1612 | } 1613 | }, 1614 | "snapdragon-util": { 1615 | "version": "3.0.1", 1616 | "resolved": "https://registry.npmjs.org/snapdragon-util/-/snapdragon-util-3.0.1.tgz", 1617 | "integrity": "sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==", 1618 | "dev": true, 1619 | "requires": { 1620 | "kind-of": "3.2.2" 1621 | }, 1622 | "dependencies": { 1623 | "kind-of": { 1624 | "version": "3.2.2", 1625 | "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", 1626 | "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", 1627 | "dev": true, 1628 | "requires": { 1629 | "is-buffer": "1.1.6" 1630 | } 1631 | } 1632 | } 1633 | }, 1634 | "source-map": { 1635 | "version": "0.5.6", 1636 | "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.6.tgz", 1637 | "integrity": "sha1-dc449SvwczxafwwRjYEzSiu19BI=", 1638 | "dev": true 1639 | }, 1640 | "source-map-resolve": { 1641 | "version": "0.5.1", 1642 | "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.1.tgz", 1643 | "integrity": "sha512-0KW2wvzfxm8NCTb30z0LMNyPqWCdDGE2viwzUaucqJdkTRXtZiSY3I+2A6nVAjmdOy0I4gU8DwnVVGsk9jvP2A==", 1644 | "dev": true, 1645 | "requires": { 1646 | "atob": "2.0.3", 1647 | "decode-uri-component": "0.2.0", 1648 | "resolve-url": "0.2.1", 1649 | "source-map-url": "0.4.0", 1650 | "urix": "0.1.0" 1651 | } 1652 | }, 1653 | "source-map-url": { 1654 | "version": "0.4.0", 1655 | "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.0.tgz", 1656 | "integrity": "sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM=", 1657 | "dev": true 1658 | }, 1659 | "speakingurl": { 1660 | "version": "14.0.1", 1661 | "resolved": "https://registry.npmjs.org/speakingurl/-/speakingurl-14.0.1.tgz", 1662 | "integrity": "sha512-1POYv7uv2gXoyGFpBCmpDVSNV74IfsWlDW216UPjbWufNf+bSU6GdbDsxdcxtfwb4xlI3yxzOTKClUosxARYrQ==" 1663 | }, 1664 | "split-string": { 1665 | "version": "3.1.0", 1666 | "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz", 1667 | "integrity": "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==", 1668 | "dev": true, 1669 | "requires": { 1670 | "extend-shallow": "3.0.2" 1671 | } 1672 | }, 1673 | "stack-trace": { 1674 | "version": "0.0.10", 1675 | "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz", 1676 | "integrity": "sha1-VHxws0fo0ytOEI6hoqFZ5f3eGcA=", 1677 | "dev": true 1678 | }, 1679 | "static-extend": { 1680 | "version": "0.1.2", 1681 | "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz", 1682 | "integrity": "sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY=", 1683 | "dev": true, 1684 | "requires": { 1685 | "define-property": "0.2.5", 1686 | "object-copy": "0.1.0" 1687 | }, 1688 | "dependencies": { 1689 | "define-property": { 1690 | "version": "0.2.5", 1691 | "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", 1692 | "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", 1693 | "dev": true, 1694 | "requires": { 1695 | "is-descriptor": "0.1.6" 1696 | } 1697 | }, 1698 | "is-accessor-descriptor": { 1699 | "version": "0.1.6", 1700 | "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", 1701 | "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", 1702 | "dev": true, 1703 | "requires": { 1704 | "kind-of": "3.2.2" 1705 | }, 1706 | "dependencies": { 1707 | "kind-of": { 1708 | "version": "3.2.2", 1709 | "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", 1710 | "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", 1711 | "dev": true, 1712 | "requires": { 1713 | "is-buffer": "1.1.6" 1714 | } 1715 | } 1716 | } 1717 | }, 1718 | "is-data-descriptor": { 1719 | "version": "0.1.4", 1720 | "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", 1721 | "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", 1722 | "dev": true, 1723 | "requires": { 1724 | "kind-of": "3.2.2" 1725 | }, 1726 | "dependencies": { 1727 | "kind-of": { 1728 | "version": "3.2.2", 1729 | "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", 1730 | "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", 1731 | "dev": true, 1732 | "requires": { 1733 | "is-buffer": "1.1.6" 1734 | } 1735 | } 1736 | } 1737 | }, 1738 | "is-descriptor": { 1739 | "version": "0.1.6", 1740 | "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", 1741 | "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", 1742 | "dev": true, 1743 | "requires": { 1744 | "is-accessor-descriptor": "0.1.6", 1745 | "is-data-descriptor": "0.1.4", 1746 | "kind-of": "5.1.0" 1747 | } 1748 | }, 1749 | "kind-of": { 1750 | "version": "5.1.0", 1751 | "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", 1752 | "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", 1753 | "dev": true 1754 | } 1755 | } 1756 | }, 1757 | "strip-ansi": { 1758 | "version": "3.0.1", 1759 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", 1760 | "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", 1761 | "requires": { 1762 | "ansi-regex": "2.1.1" 1763 | } 1764 | }, 1765 | "supports-color": { 1766 | "version": "5.3.0", 1767 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.3.0.tgz", 1768 | "integrity": "sha512-0aP01LLIskjKs3lq52EC0aGBAJhLq7B2Rd8HC/DR/PtNNpcLilNmHC12O+hu0usQpo7wtHNRqtrhBwtDb0+dNg==", 1769 | "requires": { 1770 | "has-flag": "3.0.0" 1771 | }, 1772 | "dependencies": { 1773 | "has-flag": { 1774 | "version": "3.0.0", 1775 | "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", 1776 | "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=" 1777 | } 1778 | } 1779 | }, 1780 | "temp": { 1781 | "version": "0.8.3", 1782 | "resolved": "https://registry.npmjs.org/temp/-/temp-0.8.3.tgz", 1783 | "integrity": "sha1-4Ma8TSa5AxJEEOT+2BEDAU38H1k=", 1784 | "dev": true, 1785 | "requires": { 1786 | "os-tmpdir": "1.0.2", 1787 | "rimraf": "2.2.8" 1788 | }, 1789 | "dependencies": { 1790 | "rimraf": { 1791 | "version": "2.2.8", 1792 | "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.2.8.tgz", 1793 | "integrity": "sha1-5Dm+Kq7jJzIZUnMPmaiSnk/FBYI=", 1794 | "dev": true 1795 | } 1796 | } 1797 | }, 1798 | "text-encoding": { 1799 | "version": "0.6.4", 1800 | "resolved": "https://registry.npmjs.org/text-encoding/-/text-encoding-0.6.4.tgz", 1801 | "integrity": "sha1-45mpgiV6J22uQou5KEXLcb3CbRk=", 1802 | "dev": true 1803 | }, 1804 | "to-fast-properties": { 1805 | "version": "1.0.3", 1806 | "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-1.0.3.tgz", 1807 | "integrity": "sha1-uDVx+k2MJbguIxsG46MFXeTKGkc=" 1808 | }, 1809 | "to-object-path": { 1810 | "version": "0.3.0", 1811 | "resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz", 1812 | "integrity": "sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68=", 1813 | "dev": true, 1814 | "requires": { 1815 | "kind-of": "3.2.2" 1816 | }, 1817 | "dependencies": { 1818 | "kind-of": { 1819 | "version": "3.2.2", 1820 | "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", 1821 | "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", 1822 | "dev": true, 1823 | "requires": { 1824 | "is-buffer": "1.1.6" 1825 | } 1826 | } 1827 | } 1828 | }, 1829 | "to-regex": { 1830 | "version": "3.0.2", 1831 | "resolved": "https://registry.npmjs.org/to-regex/-/to-regex-3.0.2.tgz", 1832 | "integrity": "sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==", 1833 | "dev": true, 1834 | "requires": { 1835 | "define-property": "2.0.2", 1836 | "extend-shallow": "3.0.2", 1837 | "regex-not": "1.0.2", 1838 | "safe-regex": "1.1.0" 1839 | } 1840 | }, 1841 | "to-regex-range": { 1842 | "version": "2.1.1", 1843 | "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", 1844 | "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", 1845 | "dev": true, 1846 | "requires": { 1847 | "is-number": "3.0.0", 1848 | "repeat-string": "1.6.1" 1849 | } 1850 | }, 1851 | "type-detect": { 1852 | "version": "4.0.8", 1853 | "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", 1854 | "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", 1855 | "dev": true 1856 | }, 1857 | "uevents": { 1858 | "version": "1.0.0", 1859 | "resolved": "https://registry.npmjs.org/uevents/-/uevents-1.0.0.tgz", 1860 | "integrity": "sha1-IAXppHL7CkbIJuz9GJrtmI7F6+E=" 1861 | }, 1862 | "ulog": { 1863 | "version": "1.1.0", 1864 | "resolved": "https://registry.npmjs.org/ulog/-/ulog-1.1.0.tgz", 1865 | "integrity": "sha512-oQDYGh4w/ykGSpffEuwkqFnL5BYJa5HeV5PhpkjuAFwJCyjp2KjT2YDcFa8y4ie8kejdXMwpFCSF6HNM5Kia4g==" 1866 | }, 1867 | "union-value": { 1868 | "version": "1.0.0", 1869 | "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.0.tgz", 1870 | "integrity": "sha1-XHHDTLW61dzr4+oM0IIHulqhrqQ=", 1871 | "dev": true, 1872 | "requires": { 1873 | "arr-union": "3.1.0", 1874 | "get-value": "2.0.6", 1875 | "is-extendable": "0.1.1", 1876 | "set-value": "0.4.3" 1877 | }, 1878 | "dependencies": { 1879 | "extend-shallow": { 1880 | "version": "2.0.1", 1881 | "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", 1882 | "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", 1883 | "dev": true, 1884 | "requires": { 1885 | "is-extendable": "0.1.1" 1886 | } 1887 | }, 1888 | "set-value": { 1889 | "version": "0.4.3", 1890 | "resolved": "https://registry.npmjs.org/set-value/-/set-value-0.4.3.tgz", 1891 | "integrity": "sha1-fbCPnT0i3H945Trzw79GZuzfzPE=", 1892 | "dev": true, 1893 | "requires": { 1894 | "extend-shallow": "2.0.1", 1895 | "is-extendable": "0.1.1", 1896 | "is-plain-object": "2.0.4", 1897 | "to-object-path": "0.3.0" 1898 | } 1899 | } 1900 | } 1901 | }, 1902 | "unset-value": { 1903 | "version": "1.0.0", 1904 | "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz", 1905 | "integrity": "sha1-g3aHP30jNRef+x5vw6jtDfyKtVk=", 1906 | "dev": true, 1907 | "requires": { 1908 | "has-value": "0.3.1", 1909 | "isobject": "3.0.1" 1910 | }, 1911 | "dependencies": { 1912 | "has-value": { 1913 | "version": "0.3.1", 1914 | "resolved": "https://registry.npmjs.org/has-value/-/has-value-0.3.1.tgz", 1915 | "integrity": "sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8=", 1916 | "dev": true, 1917 | "requires": { 1918 | "get-value": "2.0.6", 1919 | "has-values": "0.1.4", 1920 | "isobject": "2.1.0" 1921 | }, 1922 | "dependencies": { 1923 | "isobject": { 1924 | "version": "2.1.0", 1925 | "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", 1926 | "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", 1927 | "dev": true, 1928 | "requires": { 1929 | "isarray": "1.0.0" 1930 | } 1931 | } 1932 | } 1933 | }, 1934 | "has-values": { 1935 | "version": "0.1.4", 1936 | "resolved": "https://registry.npmjs.org/has-values/-/has-values-0.1.4.tgz", 1937 | "integrity": "sha1-bWHeldkd/Km5oCCJrThL/49it3E=", 1938 | "dev": true 1939 | } 1940 | } 1941 | }, 1942 | "urix": { 1943 | "version": "0.1.0", 1944 | "resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz", 1945 | "integrity": "sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=", 1946 | "dev": true 1947 | }, 1948 | "use": { 1949 | "version": "3.1.0", 1950 | "resolved": "https://registry.npmjs.org/use/-/use-3.1.0.tgz", 1951 | "integrity": "sha512-6UJEQM/L+mzC3ZJNM56Q4DFGLX/evKGRg15UJHGB9X5j5Z3AFbgZvjUh2yq/UJUY4U5dh7Fal++XbNg1uzpRAw==", 1952 | "dev": true, 1953 | "requires": { 1954 | "kind-of": "6.0.2" 1955 | } 1956 | }, 1957 | "which": { 1958 | "version": "1.3.0", 1959 | "resolved": "https://registry.npmjs.org/which/-/which-1.3.0.tgz", 1960 | "integrity": "sha512-xcJpopdamTuY5duC/KnTTNBraPK54YwpenP4lzxU8H91GudWpFv38u0CKjclE1Wi2EH2EDz5LRcHcKbCIzqGyg==", 1961 | "dev": true, 1962 | "requires": { 1963 | "isexe": "2.0.0" 1964 | } 1965 | }, 1966 | "wrappy": { 1967 | "version": "1.0.2", 1968 | "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", 1969 | "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", 1970 | "dev": true 1971 | } 1972 | } 1973 | } 1974 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "i18nline", 3 | "description": "Keep your translations in line", 4 | "version": "2.0.1", 5 | "scripts": { 6 | "test": "mocha \"./test/**/*_test.js\" --recursive", 7 | "i18nline": "node ./bin/i18nline.js" 8 | }, 9 | "main": "./lib/i18n", 10 | "bin": { 11 | "i18nline": "./bin/i18nline.js" 12 | }, 13 | "author": "Stijn de Witt & Jon Jensen", 14 | "license": "MIT", 15 | "homepage": "https://github.com/download/i18nline", 16 | "repository": { 17 | "type": "git", 18 | "url": "http://github.com/download/i18nline.git" 19 | }, 20 | "contributors": [ 21 | { 22 | "name": "Stijn de Witt", 23 | "email": "StijnDeWitt@hotmail.com", 24 | "website": "https://StijnDeWitt.com" 25 | }, 26 | { 27 | "name": "Jon Jensen", 28 | "email": "jenseng@gmail.com", 29 | "website": "https://github.com/jenseng" 30 | } 31 | ], 32 | "devDependencies": { 33 | "chai": "^4.1.2", 34 | "matchdep": "^2.0.0", 35 | "mocha": "^5.0.5", 36 | "pkgcfg": "^0.9.1", 37 | "rimraf": "^2.6.2", 38 | "sinon": "^4.4.8", 39 | "temp": "^0.8.3" 40 | }, 41 | "dependencies": { 42 | "babel-traverse": "^6.0.0", 43 | "babylon": "^6.0.0", 44 | "chalk": "^2.3.2", 45 | "crc32": "~0.2.2", 46 | "esprima": "^4.0.0", 47 | "extend": "^3.0.1", 48 | "gglobby": "0.0.3", 49 | "i18n-js": "^3.0.3", 50 | "minimist": "^1.2.0", 51 | "mkdirp": "^0.5.1", 52 | "speakingurl": "^14.0.1", 53 | "uevents": "^1.0.0", 54 | "ulog": "^1.1.0" 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /test/call_helpers_test.js: -------------------------------------------------------------------------------- 1 | /* global describe, it */ 2 | var assert = require('chai').assert; 3 | var CallHelpers = require('../lib/call_helpers'); 4 | 5 | describe("CallHelpers", function() { 6 | describe("applyWrappers", function() { 7 | it("should apply array wrappers", function() { 8 | assert.equal( 9 | CallHelpers.applyWrappers( 10 | "*hello* ***bob*** **lol**", 11 | ["$1", "$1", "$1"] 12 | ), 13 | "hello bob lol" 14 | ); 15 | }); 16 | 17 | it("should apply object wrappers", function() { 18 | assert.equal( 19 | CallHelpers.applyWrappers( 20 | "*hello* ***bob*** **lol**", 21 | {"*": "$1", "**": "$1", "***": "$1"} 22 | ), 23 | "hello bob lol" 24 | ); 25 | }); 26 | }); 27 | }); 28 | 29 | 30 | -------------------------------------------------------------------------------- /test/commands/check_test.js: -------------------------------------------------------------------------------- 1 | /* global describe, it */ 2 | 3 | var Check = require('../../lib/commands/check'); 4 | var I18nline = require('../../lib/i18nline'); 5 | var assert = require('chai').assert; 6 | 7 | describe('Check', function() { 8 | describe(".run", function() { 9 | it("should find errors", function() { 10 | I18nline.set('basePath', "test/fixtures", function() { 11 | var checker = new Check({silent: true}); 12 | checker.run(); 13 | assert.deepEqual( 14 | checker.translations.translations, 15 | {"welcome_name_4c6ebc3a": 'welcome, %{name}'} 16 | ); 17 | assert.equal(checker.errors.length, 1); 18 | assert.match(checker.errors[0], /^invalid signature/); 19 | }); 20 | }); 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /test/commands/export_test.js: -------------------------------------------------------------------------------- 1 | /* global describe, it */ 2 | 3 | var Export = require('../../lib/commands/export'); 4 | var I18nline = require('../../lib/i18nline'); 5 | var assert = require('chai').assert; 6 | var fs = require('fs'); 7 | var path = require('path'); 8 | var temp = require('temp'); 9 | var rimraf = require('rimraf'); 10 | 11 | describe('Export', function() { 12 | describe(".run", function() { 13 | it("should dump translations in utf8", function() { 14 | var tmpDir = temp.mkdirSync(); 15 | I18nline.set('basePath', tmpDir, function() { 16 | I18nline.set('out', 'out.json', function() { 17 | var cmd = new Export({silent: true}); 18 | cmd.checkFiles = function() { 19 | this.translations = {translations: {i18n: "Iñtërnâtiônàlizætiøn"}}; 20 | }; 21 | cmd.run(); 22 | assert.deepEqual( 23 | {en: {i18n: "Iñtërnâtiônàlizætiøn"}}, 24 | JSON.parse(fs.readFileSync(cmd.out)) 25 | ); 26 | }); 27 | }); 28 | rimraf.sync(tmpDir); 29 | }); 30 | }); 31 | }); 32 | 33 | -------------------------------------------------------------------------------- /test/commands/synch_test.js: -------------------------------------------------------------------------------- 1 | /* global describe, it */ 2 | 3 | var Synch = require('../../lib/commands/synch'); 4 | var I18nline = require('../../lib/main'); 5 | var assert = require('chai').assert; 6 | var fs = require('fs'); 7 | var path = require('path'); 8 | var temp = require('temp'); 9 | var rimraf = require('rimraf'); 10 | 11 | describe('Synch', function() { 12 | describe(".run", function() { 13 | it("should synch all internationalization files", function() { 14 | var tmpDir = temp.mkdirSync(); 15 | I18nline.set('basePath', tmpDir, function() { 16 | I18nline.set('out', 'out', function() { 17 | var cmd = new Synch({silent: true}); 18 | cmd.checkFiles = function() { 19 | this.translations = {translations: {i18n: "Iñtërnâtiônàlizætiøn"}}; 20 | }; 21 | cmd.run(); 22 | // should have generated default.json 23 | assert.deepEqual( 24 | {en: {i18n: "Iñtërnâtiônàlizætiøn"}}, 25 | JSON.parse(fs.readFileSync(path.join(cmd.out, 'default.json'))) 26 | ); 27 | // should have generated en.json 28 | assert.deepEqual( 29 | {en: {i18n: "Iñtërnâtiônàlizætiøn"}}, 30 | JSON.parse(fs.readFileSync(path.join(cmd.out, 'en.json'))) 31 | ); 32 | // should have generated index.js 33 | assert.doesNotThrow(function(){ 34 | var js = fs.readFileSync(path.join(cmd.out, 'index.js')).toString(); 35 | assert(js.startsWith('// Generated by i18nline')); 36 | }); 37 | }); 38 | }); 39 | rimraf.sync(tmpDir); 40 | }); 41 | }); 42 | }); 43 | 44 | -------------------------------------------------------------------------------- /test/extensions/i18n_js_test.js: -------------------------------------------------------------------------------- 1 | /* global describe, context, it */ 2 | var assert = require('chai').assert; 3 | var sinon = require('sinon'); 4 | var extend = require('../../lib/extensions/i18n_js'); 5 | var Utils = require('../../lib/utils'); 6 | 7 | describe("I18nJs extension", function() { 8 | var I18n = { 9 | translate: function(key, options) { 10 | var defaultValue = options.defaultValue; 11 | if (typeof options.count === "number") { 12 | defaultValue = options.count === 1 ? defaultValue.one : defaultValue.other; 13 | } 14 | return this.interpolate(defaultValue, options); 15 | }, 16 | 17 | interpolate: function(string, options) { 18 | var matches = string.match(this.PLACEHOLDER); 19 | var placeholder; 20 | if (!matches) 21 | return string; 22 | for (var i = 0, len = matches.length; i < len; i++) { 23 | placeholder = matches[i]; 24 | var name = placeholder.replace(this.PLACEHOLDER, "$1"); 25 | string = string.replace(placeholder, options[name]); 26 | } 27 | return string; 28 | } 29 | }; 30 | extend(I18n); 31 | 32 | describe("translate", function() { 33 | it("should should normalize the arguments passed into the original translate", function() { 34 | var spy = sinon.spy(I18n, "translateWithoutI18nline"); 35 | assert.equal( 36 | I18n.translate("Hello %{name}", {name: "bob"}), 37 | "Hello bob" 38 | ); 39 | assert.deepEqual( 40 | ["hello_name_84ff273f", {defaultValue: "Hello %{name}", name: "bob"}], 41 | spy.args[0] 42 | ); 43 | spy.restore(); 44 | }); 45 | 46 | it("should infer pluralization objects", function() { 47 | var spy = sinon.spy(I18n, "translateWithoutI18nline"); 48 | I18n.translate("light", {count: 1}); 49 | assert.deepEqual( 50 | ["count_lights_58339e29", {defaultValue: {one: "1 light", other: "%{count} lights"}, count: 1}], 51 | spy.args[0] 52 | ); 53 | spy.restore(); 54 | }); 55 | 56 | context("with wrappers", function() { 57 | it("should apply a single wrapper", function() { 58 | var result = I18n.translate("Hello *bob*.", {wrapper: '$1'}); 59 | assert.equal(result, "Hello bob."); 60 | }); 61 | 62 | it("should be html-safe", function() { 63 | var result = I18n.translate("Hello *bob*.", {wrapper: '$1'}); 64 | assert(result instanceof Utils.HtmlSafeString); 65 | }); 66 | 67 | it("should apply multiple wrappers", function() { 68 | var result = I18n.translate("Hello *bob*. Click **here**", {wrappers: ['$1', '$1']}); 69 | assert.equal(result, "Hello bob. Click here"); 70 | }); 71 | 72 | it("should apply multiple wrappers with arbitrary delimiters", function() { 73 | var result = I18n.translate("Hello !!!bob!!!. Click ???here???", {wrappers: {'!!!': '$1', '???': '$1'}}); 74 | assert.equal(result, "Hello bob. Click here"); 75 | }); 76 | 77 | it("should html-escape the default when applying wrappers", function() { 78 | var result = I18n.translate("*bacon* > narwhals", {wrappers: ['$1']}); 79 | assert.equal(result, "bacon > narwhals"); 80 | }); 81 | 82 | it("should interpolate placeholders in the wrapper", function() { 83 | var result = I18n.translate("ohai *click here*", {wrapper: '$1', url: "about:blank"}); 84 | assert.equal(result, 'ohai click here'); 85 | }); 86 | }); 87 | }); 88 | 89 | describe("interpolate", function() { 90 | it("should not escape anything if none of the components are html-safe", function() { 91 | var result = I18n.interpolate("hello & good day, %{name}", {name: "