├── .gitignore ├── README.md ├── commands.js ├── commands ├── create-csv.js ├── generate.js ├── lang-switcher.js └── list-codes.js ├── index.js ├── locale-codes └── index.js ├── package-lock.json ├── package.json ├── sample-csv └── translations.csv └── utils └── getLanguagesAndCodesAsObjects.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Quasalang CLI 2 | 3 | > Generate all the i18n language files your Quasar Framework app needs - from a CSV file 4 | 5 | 6 | Quasalang is a global CLI tool (for Quasar Framework projects) that allows you to generate all your i18n language files (including the main index.js file) instantly from a single, easy to update CSV file. 7 | 8 | It will also generate a sample CSV file for you, so you can easily get started. 9 | 10 | You can also: 11 | - Organise your phrases with empty lines & comments 12 | - Generate your language switcher array code 13 | - Search & list i18n locale codes easily 14 | 15 | ## Contents 16 | 17 | - [Quasalang CLI](#quasalang-cli) 18 | - [Contents](#contents) 19 | - [Getting Started](#getting-started) 20 | - [Step 1: Install globally](#step-1-install-globally) 21 | - [Step 2: Generate a Sample CSV file](#step-2-generate-a-sample-csv-file) 22 | - [Step 3: Add your own languages and translations](#step-3-add-your-own-languages-and-translations) 23 | - [Step 4: Generate your language files](#step-4-generate-your-language-files) 24 | - [Options](#options) 25 | - [Input Path](#input-path) 26 | - [Output Path](#output-path) 27 | - [Force Write](#force-write) 28 | - [No Watermark](#no-watermark) 29 | - [Language Switcher Array Code](#language-switcher-array-code) 30 | - [Watch for changes](#watch-for-changes) 31 | - [Advanced Features](#advanced-features) 32 | - [Use Empty Rows to Split Up Your Phrases](#use-empty-rows-to-split-up-your-phrases) 33 | - [Use Comments to Organise your Phrases](#use-comments-to-organise-your-phrases) 34 | - [Use Strings as Your Keys](#use-strings-as-your-keys) 35 | - [Add Multi-Line Phrases](#add-multi-line-phrases) 36 | - [Extra Tools](#extra-tools) 37 | - [Generate language switcher array code](#generate-language-switcher-array-code) 38 | - [Search and List i18n Locale Codes](#search-and-list-i18n-locale-codes) 39 | - [Commands](#commands) 40 | - [`generate`](#generate) 41 | - [`create-csv`](#create-csv) 42 | - [`lang-switcher`](#lang-switcher) 43 | - [`list-codes`](#list-codes) 44 | 45 | ## Getting Started 46 | 47 | ### Step 1: Install globally 48 | ```bash 49 | $ npm install -g quasalang 50 | ``` 51 | 52 | Once installed, get yourself to the root of a Quasar project 53 | 54 | ```bash 55 | $ cd my-quasar-project 56 | ``` 57 | 58 | ### Step 2: Generate a Sample CSV file 59 | ```bash 60 | $ quasalang create-csv 61 | ``` 62 | 63 | This will generate a CSV file at `/translations.csv` that looks like this: 64 | 65 | | Key | English, en-US | French, fr | German, de | 66 | |---------|----------------|------------|-----------------| 67 | | hello | Hello | Bonjour | Hallo | 68 | | goodbye | Goodbye | Au revoir | Auf Wiedersehen | 69 | | thanks | Thanks | Merci | Danke | 70 | 71 |
72 | View Source 73 | 74 | ```csv 75 | Key,"English, en-US","French, fr","German, de" 76 | hello,"Hello","Bonjour","Hallo" 77 | goodbye,"Goodbye","Au revoir","Auf Wiedersehen" 78 | thanks,"Thanks","Merci","Danke" 79 | ``` 80 |
81 | 82 | ### Step 3: Add your own languages and translations 83 | 84 | Use a CSV editor (such as the [VSCode Extension "Edit csv"](https://marketplace.visualstudio.com/items?itemName=janisdd.vscode-edit-csv)) to add your own languages & phrases. 85 | 86 | **Be sure to use the format** `Language, code` in the header row e.g. `Russian, ru`: 87 | 88 | | Key | English, en-US | French, fr | German, de | Russian, ru | 89 | |----------|----------------|---------------------|-----------------|-----------------| 90 | | hello | Hello | Bonjour | Hallo | Привет | 91 | | goodbye | Goodbye | Au revoir | Auf Wiedersehen | До свидания | 92 | | thanks | Thanks | Merci | Danke | Спасибо | 93 | | buttHair | Butt hair | Cheveux bout à bout | Hintern Haare | стыковые волосы | 94 | 95 |
96 | View Source 97 | 98 | ```csv 99 | Key,"English, en-US","French, fr","German, de","Russian, ru" 100 | hello,"Hello","Bonjour","Hallo",Привет 101 | goodbye,"Goodbye","Au revoir","Auf Wiedersehen",До свидания 102 | thanks,"Thanks","Merci","Danke",Спасибо 103 | buttHair,"Butt hair","Cheveux bout à bout","Hintern Haare",стыковые волосы 104 | ``` 105 |
106 | 107 | ### Step 4: Generate your language files 108 | 109 | Generate all the language files you need based on your CSV: 110 | 111 | ```bash 112 | $ quasalang generate 113 | ``` 114 | 115 | By default, this will generate (or overwrite) your `/src/i18n` folder, generating all the files and folders you need: 116 | 117 | ``` 118 | src/ 119 | ├─ i18n/ 120 | │ ├─ de/ 121 | │ │ ├─ index.js 122 | │ ├─ en-US/ 123 | │ │ ├─ index.js 124 | │ ├─ fr/ 125 | │ │ ├─ index.js 126 | │ ├─ ru/ 127 | │ │ ├─ index.js 128 | │ ├─ index.js 129 | ``` 130 | 131 | Your main index file `/src/i18n/index.js` will look like this: 132 | 133 | ```javascript 134 | // This file was auto-generated by Quasalang 135 | 136 | import enUS from './en-US' 137 | import fr from './fr' 138 | import de from './de' 139 | import ru from './ru' 140 | 141 | export default { 142 | 'en-US': enUS, // English 143 | 'fr': fr, // French 144 | 'de': de, // German 145 | 'ru': ru, // Russian 146 | } 147 | ``` 148 | 149 | And your language files, e.g. `/src/i18n/ru/index.js` will look like this: 150 | 151 | ```javascript 152 | // Russian, ru 153 | // This file was auto-generated by Quasalang 154 | 155 | export default { 156 | hello: `Привет`, 157 | goodbye: `До свидания`, 158 | thanks: `Спасибо`, 159 | buttHair: `стыковые волосы`, 160 | } 161 | ``` 162 | 163 | ## Options 164 | 165 | ### Input Path 166 | 167 | The default input path is `/translations.csv` but you can change it if you like: 168 | 169 | ```bash 170 | $ quasalang generate --input /files/my-translations.csv 171 | 172 | # or the shorthand... 173 | 174 | $ quasalang g -i /files/my-translations.csv 175 | ``` 176 | 177 | ### Output Path 178 | 179 | The default output path is `/src/i18n` but you can change it if you like: 180 | 181 | ```bash 182 | $ quasalang generate --output /src/my-translations 183 | 184 | # or the shorthand... 185 | 186 | $ quasalang g -o /src/my-translations 187 | ``` 188 | 189 | ### Force Write 190 | 191 | By default, if the output folder exists, you'll be prompted to overwrite it: 192 | 193 | ```bash 194 | ? Folder src/i18n exists. Overwrite it? (Y/n) 195 | ``` 196 | 197 | You can skip this prompt if you like: 198 | 199 | ```bash 200 | $ quasalang generate --force 201 | 202 | # or the shorthand... 203 | 204 | $ quasalang g -f 205 | ``` 206 | 207 | ### No Watermark 208 | 209 | By default, Quasalang will add a watermark to your files: 210 | 211 | ```javascript 212 | // This file was auto-generated by Quasalang 213 | ``` 214 | 215 | You can disable this if you like: 216 | 217 | ```bash 218 | $ quasalang generate --nowatermark 219 | 220 | # or the shorthand... 221 | 222 | $ quasalang g -nw 223 | ``` 224 | 225 | ### Language Switcher Array Code 226 | 227 | You also generate the array code for your language switcher: 228 | 229 | ```bash 230 | $ quasalang generate --lang-switcher 231 | 232 | # or the shorthand... 233 | 234 | $ quasalang g -ls 235 | ``` 236 | 237 | See [Generate language switcher array code](#generate-language-switcher-array-code) for more info. 238 | 239 | ### Watch for changes 240 | 241 | You can watch for changes to your translation file and auto-regenerate your language files. You can then just leave it running in the background and forget about it: 242 | 243 | ```bash 244 | $ quasalang generate --watch 245 | 246 | # or the shorthand... 247 | 248 | $ quasalang g -w 249 | ``` 250 | 251 | ## Advanced Features 252 | 253 | ### Use Empty Rows to Split Up Your Phrases 254 | 255 | You can leave empty rows in your CSV file, like this: 256 | 257 | | Key | English, en-US | French, fr | German, de | Russian, ru | 258 | |----------|----------------|---------------------|-----------------|-----------------| 259 | |   | | | | | 260 | | hello | Hello | Bonjour | Hallo | Привет | 261 | | goodbye | Goodbye | Au revoir | Auf Wiedersehen | До свидания | 262 | | thanks | Thanks | Merci | Danke | Спасибо | 263 | |   | | | | | 264 | | buttHair | Butt hair | Cheveux bout à bout | Hintern Haare | стыковые волосы | 265 | |   | | | | | 266 | 267 |
268 | View Source 269 | 270 | ```csv 271 | Key,"English, en-US","French, fr","German, de","Russian, ru" 272 | ,,,, 273 | hello,"Hello","Bonjour","Hallo",Привет 274 | goodbye,"Goodbye","Au revoir","Auf Wiedersehen",До свидания 275 | thanks,"Thanks","Merci","Danke",Спасибо 276 | ,,,, 277 | buttHair,"Butt hair","Cheveux bout à bout","Hintern Haare",стыковые волосы 278 | ,,,, 279 | ``` 280 |
281 | 282 | And this will generate equivalent empty lines in your generated language files: 283 | 284 | ```javascript 285 | // Russian, ru 286 | // This file was auto-generated by Quasalang 287 | 288 | export default { 289 | 290 | hello: `Привет`, 291 | goodbye: `До свидания`, 292 | thanks: `Спасибо`, 293 | 294 | buttHair: `стыковые волосы`, 295 | 296 | } 297 | ``` 298 | 299 | ### Use Comments to Organise your Phrases 300 | 301 | You can add comments to your CSV file to create sections like this: 302 | 303 | | Key | English, en-US | French, fr | German, de | Russian, ru | 304 | |----------------|----------------|---------------------|-----------------|-----------------| 305 | |   | | | | | 306 | | `# Greetings` | | | | | 307 | | hello | Hello | Bonjour | Hallo | Привет | 308 | | goodbye | Goodbye | Au revoir | Auf Wiedersehen | До свидания | 309 | | thanks | Thanks | Merci | Danke | Спасибо | 310 | |   | | | | | 311 | | `# Hair Related` | | | | | 312 | | buttHair | Butt hair | Cheveux bout à bout | Hintern Haare | стыковые волосы | 313 | |   | | | | | 314 | 315 |
316 | View Source 317 | 318 | ```csv 319 | Key,"English, en-US","French, fr","German, de","Russian, ru" 320 | ,,,, 321 | # Greetings 322 | hello,"Hello","Bonjour","Hallo",Привет 323 | goodbye,"Goodbye","Au revoir","Auf Wiedersehen",До свидания 324 | thanks,"Thanks","Merci","Danke",Спасибо 325 | ,,,, 326 | # Hair Related 327 | buttHair,"Butt hair","Cheveux bout à bout","Hintern Haare",стыковые волосы 328 | ,,,, 329 | ``` 330 |
331 | 332 | And this will add equivalent comments to your generated files: 333 | 334 | ```javascript 335 | // Russian, ru 336 | // This file was auto-generated by Quasalang 337 | 338 | export default { 339 | 340 | // Greetings 341 | hello: `Привет`, 342 | goodbye: `До свидания`, 343 | thanks: `Спасибо`, 344 | 345 | // Hair Related 346 | buttHair: `стыковые волосы`, 347 | 348 | } 349 | ``` 350 | 351 | ### Use Strings as Your Keys 352 | 353 | If you want to use strings as your keys, just surround your keys in double quotes: 354 | 355 | | Key | English, en-US | French, fr | German, de | 356 | |---------------|----------------|------------|-----------------| 357 | | **"Hello"** | Hello | Bonjour | Hallo | 358 | | **"Goodbye"** | Goodbye | Au revoir | Auf Wiedersehen | 359 | | **"Thanks"** | Thanks | Merci | Danke | 360 | 361 |
362 | View Source 363 | 364 | ```csv 365 | Key,"English, en-US","French, fr","German, de" 366 | """Hello""","Hello","Bonjour","Hallo" 367 | """Goodbye""","Goodbye","Au revoir","Auf Wiedersehen" 368 | """Thanks""","Thanks","Merci","Danke" 369 | ``` 370 |
371 | 372 | This will generate language files like this: 373 | 374 | ```javascript 375 | // French, fr 376 | // This file was auto-generated by Quasalang 377 | 378 | export default { 379 | "Hello": `Bonjour`, 380 | "Goodbye": `Au revoir`, 381 | "Thanks": `Merci`, 382 | } 383 | ``` 384 | 385 | ### Add Multi-Line Phrases 386 | 387 | You can add multi-line phrases, like in the last row here: 388 | 389 | | Key | English, en-US | French, fr | German, de | 390 | |---------|--------------------------------------------------------|----------------------------------------------------------------|----------------------------------------------------------| 391 | | hello | Hello | Bonjour | Hallo | 392 | | goodbye | Goodbye | Au revoir | Auf Wiedersehen | 393 | | thanks | Thanks | Merci | Danke | 394 | | welcome | Hey there...
Welcome to the app!...

Hope you like it! | Salut...
Bienvenue dans l'appli! ...

J'espère que vous aimez! | Sie da...
Willkommen in der App! ...

Hoffe du magst es! | 395 | 396 |
397 | View Source 398 | 399 | ```csv 400 | Key,"English, en-US","French, fr","German, de" 401 | hello,"Hello","Bonjour","Hallo" 402 | goodbye,"Goodbye","Au revoir","Auf Wiedersehen" 403 | thanks,"Thanks","Merci","Danke" 404 | welcome,"Hey there... 405 | Welcome to the app!... 406 | 407 | Hope you like it!","Salut... 408 | Bienvenue dans l'appli! ... 409 | 410 | J'espère que vous aimez!","Sie da... 411 | Willkommen in der App! ... 412 | 413 | Hoffe du magst es!" 414 | ``` 415 |
416 | 417 | This will generate language files like this: 418 | 419 | ```javascript 420 | // English, en-US 421 | // This file was auto-generated by Quasalang 422 | 423 | export default { 424 | hello: `Hello`, 425 | goodbye: `Goodbye`, 426 | thanks: `Thanks`, 427 | welcome: `Hey there... 428 | Welcome to the app!... 429 | 430 | Hope you like it!`, 431 | } 432 | ``` 433 | 434 | ## Extra Tools 435 | 436 | ### Generate language switcher array code 437 | 438 | You can generate the code for your language switcher array (based on your CSV): 439 | 440 | ```bash 441 | $ quasalang lang-switcher 442 | 443 | # or the shorthand... 444 | 445 | $ quasalang ls 446 | ``` 447 | 448 | This will output something like this to the console: 449 | 450 | ```javascript 451 | Your language switcher options array: 452 | 453 | [ 454 | { label: 'English', value: 'en-US' }, 455 | { label: 'French', value: 'fr' }, 456 | { label: 'German', value: 'de' } 457 | ] 458 | ``` 459 | 460 | You can also run this command automatically when you use the `generate` command to generate your language files: 461 | 462 | ```bash 463 | $ quasalang generate --lang-switcher 464 | 465 | # or the shorthand... 466 | 467 | $ quasalang g -ls 468 | ``` 469 | 470 | The output will be something like this 471 | ``` 472 | Wrote 4 files: 473 | ┌─────────┬───────────────────┬─────────┬───────────────────────────┐ 474 | │ (index) │ File │ Code │ Path │ 475 | ├─────────┼───────────────────┼─────────┼───────────────────────────┤ 476 | │ 0 │ 'Main index file' │ '' │ 'src/i18n/index.js' │ 477 | │ 1 │ 'English' │ 'en-US' │ 'src/i18n/en-US/index.js' │ 478 | │ 2 │ 'French' │ 'fr' │ 'src/i18n/fr/index.js' │ 479 | │ 3 │ 'German' │ 'de' │ 'src/i18n/de/index.js' │ 480 | └─────────┴───────────────────┴─────────┴───────────────────────────┘ 481 | 482 | Your language switcher options array: 483 | 484 | [ 485 | { label: 'English', value: 'en-US' }, 486 | { label: 'French', value: 'fr' }, 487 | { label: 'German', value: 'de' } 488 | ] 489 | ``` 490 | 491 | ### Search and List i18n Locale Codes 492 | 493 | Don't know the locale code for a language? Just search for it: 494 | 495 | ```bash 496 | $ quasalang list-codes 497 | 498 | ? Enter a search query (e.g. "russian") or hit Enter to list all codes: italian 499 | 500 | 501 | _____ 502 | |_ _| 503 | | | 504 | | | 505 | _| |_ 506 | |_____| 507 | 508 | 509 | Italian, it 510 | Italian (Italy), it_IT 511 | Italian (Switzerland), it_CH 512 | ``` 513 | 514 | Or just hit enter to list them all: 515 | 516 | ```bash 517 | 518 | /\ 519 | / \ 520 | / /\ \ 521 | / ____ \ 522 | /_/ \_\ 523 | 524 | 525 | Afrikaans, af 526 | Afrikaans (Namibia), af_NA 527 | Afrikaans (South Africa), af_ZA 528 | Akan, ak 529 | Akan (Ghana), ak_GH 530 | Albanian, sq 531 | Albanian (Albania), sq_AL 532 | Amharic, am 533 | Amharic (Ethiopia), am_ET 534 | Arabic, ar 535 | Arabic (Algeria), ar_DZ 536 | Arabic (Bahrain), ar_BH 537 | Arabic (Egypt), ar_EG 538 | Arabic (Iraq), ar_IQ 539 | Arabic (Jordan), ar_JO 540 | Arabic (Kuwait), ar_KW 541 | Arabic (Lebanon), ar_LB 542 | Arabic (Libya), ar_LY 543 | Arabic (Morocco), ar_MA 544 | Arabic (Oman), ar_OM 545 | Arabic (Qatar), ar_QA 546 | Arabic (Saudi Arabia), ar_SA 547 | Arabic (Sudan), ar_SD 548 | Arabic (Syria), ar_SY 549 | Arabic (Tunisia), ar_TN 550 | Arabic (United Arab Emirates), ar_AE 551 | Arabic (Yemen), ar_YE 552 | Armenian, hy 553 | Armenian (Armenia), hy_AM 554 | Assamese (India), as_IN 555 | Assamese, as 556 | Asu, asa 557 | Asu (Tanzania), asa_TZ 558 | Azerbaijani, az 559 | Azerbaijani (Cyrillic), az_Cyrl 560 | Azerbaijani (Cyrillic, Azerbaijan), az_Cyrl_AZ 561 | Azerbaijani (Latin), az_Latn 562 | Azerbaijani (Latin, Azerbaijan), az_Latn_AZ 563 | 564 | 565 | ____ 566 | | _ \ 567 | | |_) | 568 | | _ < 569 | | |_) | 570 | |____/ 571 | 572 | 573 | Bambara, bm 574 | Bambara (Mali), bm_ML 575 | Basque, eu 576 | Basque (Spain), eu_ES 577 | 578 | ... etc ... 579 | ``` 580 | 581 | You can then copy and paste your language name and code straight into your CSV column header. 582 | 583 | ## Commands 584 | 585 | ### `generate` 586 | 587 | ``` 588 | Usage: quasalang generate|g [options] 589 | 590 | Generate your i18n folder & all language files based on a CSV file 591 | 592 | Options: 593 | -i, --input Path to input CSV (default: "translations.csv") 594 | -o, --output Path to i18n output folder (default: "src/i18n") 595 | -f, --force Force write files (without prompt) (default: false) 596 | -nw, --nowatermark Disable the watermark ("This file was auto-generated..") (default: 597 | false) 598 | -ls, --lang-switcher Generate language switcher options array & output to console i.e. [{ 599 | label: 'English', value: 'en-US'}, ..] (default: false) 600 | -w, --watch Watch CSV file for changes & regenerate files (default: false) 601 | -h, --help display help for command 602 | ``` 603 | 604 | ### `create-csv` 605 | 606 | ``` 607 | Usage: quasalang create-csv|c [options] 608 | 609 | Create a sample CSV file (/translations.csv) 610 | 611 | Options: 612 | -f, --force Force overwrite translations file (without prompt) (default: false) 613 | -h, --help display help for command 614 | ``` 615 | 616 | ### `lang-switcher` 617 | 618 | ``` 619 | Usage: quasalang lang-switcher|ls [options] 620 | 621 | Generate language switcher options array & output to console i.e. [{ label: 'English', value: 'en-US'}, ..] 622 | 623 | Options: 624 | -i, --input Path to input CSV (default: "translations.csv") 625 | -h, --help display help for command 626 | ``` 627 | 628 | ### `list-codes` 629 | 630 | ``` 631 | Usage: quasalang list-codes|lc [options] 632 | 633 | Search & list i18n locale codes 634 | 635 | Options: 636 | -h, --help display help for command 637 | ``` 638 | 639 | 640 | -------------------------------------------------------------------------------- /commands.js: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env node 2 | 3 | let pjson = require('./package.json') 4 | 5 | const program = require('commander') 6 | const { 7 | createCSV, 8 | generate, 9 | langSwitcher, 10 | listCodes 11 | } = require('./index.js') 12 | 13 | const helpText = ` 14 | 15 | Getting Started 16 | =============== 17 | 18 | Step 1. Create a sample CSV file (/translations.csv): 19 | $ quasalang create-csv 20 | 21 | Step 2. Add your own languages & phrases to /translations.csv 22 | 23 | Step 3. Generate your language files: 24 | $ quasalang generate 25 | ` 26 | 27 | program 28 | .version(pjson.version) 29 | .description('Generate Quasar i18n language files from a CSV file. Run it from the root of a Quasar project.') 30 | .addHelpText('after', helpText) 31 | 32 | program 33 | .command('generate') 34 | .alias('g') 35 | .option('-i, --input ', 'Path to input CSV', 'translations.csv') 36 | .option('-o, --output ', 'Path to i18n output folder', 'src/i18n') 37 | .option('-f, --force', 'Force write files (without prompt)', false) 38 | .option('-nw, --nowatermark', 'Disable the watermark ("This file was auto-generated..") ', false) 39 | .option('-ls, --lang-switcher', `Generate language switcher options array & output to console i.e. [{ label: 'English', value: 'en-US'}, ..]`, false) 40 | .option('-w, --watch', `Watch CSV file for changes & regenerate files`, false) 41 | .description('Generate your i18n folder & all language files based on a CSV file') 42 | .action((options) => { 43 | generate(options) 44 | }) 45 | 46 | program 47 | .command('create-csv') 48 | .alias('c') 49 | .option('-f, --force', 'Force overwrite translations file (without prompt)', false) 50 | .description('Create a sample CSV file (/translations.csv)') 51 | .action((options) => { 52 | createCSV(options) 53 | }) 54 | 55 | program 56 | .command('lang-switcher') 57 | .alias('ls') 58 | .option('-i, --input ', 'Path to input CSV', 'translations.csv') 59 | .description(`Generate language switcher options array & output to console i.e. [{ label: 'English', value: 'en-US'}, ..]`) 60 | .action((options) => { 61 | langSwitcher(options) 62 | }) 63 | 64 | program 65 | .command('list-codes') 66 | .alias('lc') 67 | .description(`Search & list i18n locale codes`) 68 | .action((options) => { 69 | listCodes(options) 70 | }) 71 | 72 | program.parse(process.argv) -------------------------------------------------------------------------------- /commands/create-csv.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs') 2 | 3 | const { prompt } = require('inquirer') 4 | 5 | module.exports = function() { 6 | this.createCSV = function(options) { 7 | let outputPath = 'translations.csv' 8 | 9 | // check if output file exists 10 | if (fs.existsSync(outputPath)) { 11 | if (!options.force) { 12 | prompt([ 13 | { 14 | type: 'confirm', 15 | name: 'confirmOverwrite', 16 | message: `File ${outputPath} exists. Overwrite it?` 17 | } 18 | ]).then(answers => { 19 | if (answers.confirmOverwrite) { 20 | console.log('INFO: Skip this prompt in future with the --force (or -f) option.') 21 | copyFile() 22 | } 23 | }) 24 | } 25 | else { 26 | copyFile() 27 | } 28 | } 29 | else { 30 | copyFile() 31 | } 32 | 33 | function copyFile() { 34 | fs.copyFile(`${__dirname}/../sample-csv/translations.csv`, `${outputPath}`, (err) => { 35 | if (err) throw err 36 | console.log(`/translations.csv was generated.`) 37 | }) 38 | } 39 | } 40 | } -------------------------------------------------------------------------------- /commands/generate.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs') 2 | const rimraf = require('rimraf') 3 | const csv = require('csv-parser') 4 | 5 | const { prompt } = require('inquirer'); 6 | 7 | require('./lang-switcher.js')(); 8 | require('../utils/getLanguagesAndCodesAsObjects.js')(); 9 | 10 | module.exports = function() { 11 | 12 | this.generate = function(options) { 13 | 14 | const watermark = 'This file was auto-generated by Quasalang' 15 | let watching = false 16 | 17 | // sanitize options.input & options.output 18 | if (options.input.startsWith('/')) options.input = options.input.substring(1) 19 | if (options.output.startsWith('/')) options.output = options.output.substring(1) 20 | 21 | // create csv and write all files 22 | readCSVAndWrite() 23 | 24 | function readCSVAndWrite() { 25 | 26 | // somewhere to store message to list files written 27 | let filesWrittenMessage = [] 28 | 29 | // array for results 30 | let results = []; 31 | 32 | // read the csv file 33 | fs.createReadStream(options.input) 34 | .pipe(csv()) 35 | .on('data', (data) => results.push(data)) 36 | .on('end', () => { 37 | 38 | let languagesAndCodesAsObjects = getLanguagesAndCodesAsObjects(results) 39 | 40 | // initialize main index file 41 | let mainIndexFile = `` 42 | 43 | // add watermark 44 | if (!options.nowatermark) { 45 | mainIndexFile += `// ${watermark}\n\n` 46 | } 47 | 48 | // generate main index file import statements 49 | languagesAndCodesAsObjects.forEach(langObj => { 50 | mainIndexFile += `import ${langObj.codeAsVariable} from './${langObj.code}'\n` 51 | }); 52 | 53 | // generate main index file export statement 54 | mainIndexFile += `\n` 55 | mainIndexFile += `export default { \n` 56 | languagesAndCodesAsObjects.forEach(langObj => { 57 | mainIndexFile += `\t'${langObj.code}': ${langObj.codeAsVariable}, // ${langObj.lang}\n` 58 | }); 59 | mainIndexFile += `}` 60 | 61 | // check if output folder exists & prompt to confirm 62 | if (fs.existsSync(options.output)) { 63 | if (!options.force) { 64 | prompt([ 65 | { 66 | type: 'confirm', 67 | name: 'confirmDeleteOutputFolder', 68 | message: `Folder ${options.output} exists. Overwrite it?` 69 | } 70 | ]).then(answers => { 71 | if (answers.confirmDeleteOutputFolder) { 72 | console.log('INFO: Skip this prompt in future with the --force (or -f) option.') 73 | deleteOutputFolder() 74 | } 75 | }) 76 | } 77 | else { 78 | deleteOutputFolder() 79 | } 80 | } 81 | else { 82 | writeFiles() 83 | } 84 | 85 | // delete the output folder if it exists 86 | function deleteOutputFolder() { 87 | if (fs.existsSync(options.output)) { 88 | // try { 89 | // fs.rmdirSync(options.output, { recursive: true }); 90 | // writeFiles() 91 | // } catch (err) { 92 | // console.error(`Error while deleting ${options.output}.`); 93 | // console.error(err) 94 | // } 95 | rimraf(options.output, function (err) { 96 | if (err) { 97 | console.error(`Error while deleting ${options.output}.`); 98 | console.error(err) 99 | } 100 | else { 101 | writeFiles() 102 | } 103 | }) 104 | } 105 | else { 106 | writeFiles() 107 | } 108 | } 109 | 110 | // write files 111 | function writeFiles() { 112 | // write the output folder if it doesn't exist 113 | if (!fs.existsSync(options.output)){ 114 | fs.mkdirSync(options.output, { recursive: true }); 115 | } 116 | 117 | // write the main index file 118 | fs.writeFile(`${options.output}/index.js`, mainIndexFile, function(err) { 119 | if(err) { 120 | return console.log(err); 121 | } 122 | filesWrittenMessage.push({ 123 | 'File': 'Main index file', 124 | 'Code': '', 125 | 'Path': `${options.output}/index.js`, 126 | }) 127 | 128 | // generate individual language folders and index.js files 129 | let languageFilesWritten = 0 130 | languagesAndCodesAsObjects.forEach(langObj => { 131 | 132 | // create language folder 133 | if (!fs.existsSync(`${options.output}/${langObj.code}`)){ 134 | fs.mkdirSync(`${options.output}/${langObj.code}`); 135 | } 136 | 137 | // generate language index file 138 | let languageIndexFile = `` 139 | 140 | // add language comment to the top 141 | languageIndexFile += `// ${langObj.lang}, ${langObj.code}` 142 | 143 | // add watermark 144 | if (!options.nowatermark) { 145 | languageIndexFile += `\n// ${watermark}` 146 | } 147 | 148 | // add blank lines 149 | languageIndexFile += `\n\n` 150 | 151 | // add opening export statement 152 | languageIndexFile += `export default {\n` 153 | 154 | // add translations 155 | results.forEach(result => { 156 | // row is not empty 157 | if (result.Key) { 158 | // add a comment if csv row is a comment 159 | if (result.Key.startsWith('#')) { 160 | languageIndexFile += `\t// ${result.Key.substring(1).trim()}\n` 161 | } 162 | // or just add the phrase key pair 163 | else { 164 | languageIndexFile += `\t` 165 | let phraseKeyPair = `${result.Key}: \`${result[langObj.langAndCode]}\`,` 166 | // if no phrase provided, comment it out 167 | if (!result[langObj.langAndCode]) { 168 | phraseKeyPair = `// ${phraseKeyPair} // no phrase provided - fallback to default` 169 | } 170 | languageIndexFile += phraseKeyPair 171 | languageIndexFile += `\n` 172 | } 173 | } 174 | // row is empty, add a blank line 175 | else { 176 | languageIndexFile += `\n` 177 | } 178 | }); 179 | 180 | // add closing brace 181 | languageIndexFile += `}` 182 | 183 | // write the language index file 184 | let languageIndexFilePath = `${options.output}/${langObj.code}/index.js` 185 | fs.writeFile(`${languageIndexFilePath}`, languageIndexFile, function(err) { 186 | if(err) { 187 | return console.log(err); 188 | } 189 | filesWrittenMessage.push({ 190 | 'File': `${langObj.lang}`, 191 | 'Code': `${langObj.code}`, 192 | 'Path': `${languageIndexFilePath}`, 193 | }) 194 | languageFilesWritten++ 195 | if (languageFilesWritten === languagesAndCodesAsObjects.length) { 196 | logWriteMessages() 197 | } 198 | }) 199 | 200 | }); 201 | }) 202 | } 203 | 204 | // log write messages 205 | function logWriteMessages() { 206 | console.log(`\nWrote ${filesWrittenMessage.length} files:`) 207 | console.table(filesWrittenMessage) 208 | 209 | if (options.langSwitcher) { 210 | console.log('') 211 | langSwitcher({ input: options.input }) 212 | } 213 | 214 | setupWatcher() 215 | } 216 | 217 | }) 218 | } 219 | 220 | function setupWatcher() { 221 | if (options.watch) { 222 | setTimeout(() => { 223 | timestampLog(`Watching ${options.input} for changes...`) 224 | 225 | if (!watching) { 226 | watching = true 227 | fs.watchFile(options.input, { interval: 1000 },(curr, prev) => { 228 | timestampLog(`File ${options.input} changed.`) 229 | timestampLog(`Regenerating language files...`) 230 | options.force = true 231 | readCSVAndWrite(options) 232 | }) 233 | } 234 | 235 | }, 500) 236 | } 237 | } 238 | 239 | function timestampLog(message) { 240 | console.log(`[${new Date().toLocaleString()}] ${message}`) 241 | } 242 | 243 | } 244 | } -------------------------------------------------------------------------------- /commands/lang-switcher.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs') 2 | const csv = require('csv-parser') 3 | 4 | require('../utils/getLanguagesAndCodesAsObjects.js')(); 5 | 6 | module.exports = function() { 7 | this.langSwitcher = function(options) { 8 | 9 | let results = [] 10 | 11 | // sanitize options.input & outputPath 12 | if (options.input.startsWith('/')) options.input = options.input.substring(1) 13 | 14 | // read the csv file 15 | fs.createReadStream(options.input) 16 | .pipe(csv()) 17 | .on('data', (data) => results.push(data)) 18 | .on('end', () => { 19 | let languagesAndCodesAsObjects = getLanguagesAndCodesAsObjects(results) 20 | 21 | let languageSwitcherOptions = [] 22 | 23 | languagesAndCodesAsObjects.forEach(langObj => { 24 | let optionObj = { 25 | label: langObj.lang, 26 | value: langObj.code 27 | } 28 | languageSwitcherOptions.push(optionObj) 29 | }); 30 | 31 | console.log('Your language switcher options array:') 32 | console.log('') 33 | console.log(languageSwitcherOptions) 34 | console.log('') 35 | }) 36 | } 37 | } -------------------------------------------------------------------------------- /commands/list-codes.js: -------------------------------------------------------------------------------- 1 | const colors = require('colors') 2 | let figlet = require('figlet') 3 | const { prompt } = require('inquirer') 4 | 5 | module.exports = function() { 6 | this.listCodes = function(options) { 7 | 8 | // get raw locale codes object 9 | let localeCodes = require('../locale-codes') 10 | 11 | // convert object into workable array 12 | let localeCodesArray = [] 13 | Object.keys(localeCodes).forEach(code => { 14 | // console.log(`key=${code} value=${localeCodes[code]}`) 15 | let localeCodeObj = { 16 | language: localeCodes[code], 17 | code: code, 18 | } 19 | localeCodesArray.push(localeCodeObj) 20 | }) 21 | 22 | // prompt for search string 23 | console.log('') 24 | prompt([ 25 | { 26 | type: 'input', 27 | name: 'search', 28 | message: `Enter a search query (e.g. "russian") or hit Enter to list all codes:` 29 | } 30 | ]).then(answers => { 31 | if (answers.search) { 32 | filterCodesBySearch(answers.search) 33 | } 34 | else { 35 | listCodes(localeCodesArray) 36 | } 37 | }) 38 | 39 | 40 | function filterCodesBySearch(search) { 41 | let localeCodesFilteredBySearch = localeCodesArray.filter(obj => { 42 | return (obj.language.toLowerCase().includes(search.toLowerCase())); 43 | }) 44 | if (localeCodesFilteredBySearch.length) { 45 | listCodes(localeCodesFilteredBySearch) 46 | } 47 | else { 48 | noResults() 49 | } 50 | } 51 | 52 | function listCodes(codes) { 53 | // loop through alphabet 54 | let alphabet = ['A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z'] 55 | 56 | alphabet.forEach(letter => { 57 | let localeCodesByLetter = codes.filter(obj => { 58 | return (obj.language.startsWith(letter)); 59 | }); 60 | 61 | if (localeCodesByLetter.length) { 62 | 63 | // print letter header 64 | console.log('\n') 65 | console.log(colors.green(figlet.textSync(letter, { 66 | font: 'big', 67 | width: 80, 68 | whitespaceBreak: false 69 | }))); 70 | 71 | // print letter results 72 | localeCodesByLetter.forEach(item => { 73 | console.log(`${colors.cyan.bold(item.language)}, ${colors.yellow(item.code)}`) 74 | }) 75 | 76 | } 77 | 78 | }); 79 | 80 | console.log('\n\n') 81 | } 82 | 83 | function noResults() { 84 | console.log(colors.yellow(figlet.textSync('No results found.', { 85 | font: 'big', 86 | width: 80, 87 | whitespaceBreak: true 88 | }))); 89 | } 90 | 91 | 92 | } 93 | } -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | require('./commands/create-csv.js')(); 2 | require('./commands/generate.js')(); 3 | require('./commands/lang-switcher.js')(); 4 | require('./commands/list-codes.js')(); 5 | 6 | module.exports = { 7 | createCSV, 8 | generate, 9 | langSwitcher, 10 | listCodes 11 | } -------------------------------------------------------------------------------- /locale-codes/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | af: "Afrikaans", 3 | af_NA: "Afrikaans (Namibia)", 4 | af_ZA: "Afrikaans (South Africa)", 5 | ak: "Akan", 6 | ak_GH: "Akan (Ghana)", 7 | sq: "Albanian", 8 | sq_AL: "Albanian (Albania)", 9 | am: "Amharic", 10 | am_ET: "Amharic (Ethiopia)", 11 | ar: "Arabic", 12 | ar_DZ: "Arabic (Algeria)", 13 | ar_BH: "Arabic (Bahrain)", 14 | ar_EG: "Arabic (Egypt)", 15 | ar_IQ: "Arabic (Iraq)", 16 | ar_JO: "Arabic (Jordan)", 17 | ar_KW: "Arabic (Kuwait)", 18 | ar_LB: "Arabic (Lebanon)", 19 | ar_LY: "Arabic (Libya)", 20 | ar_MA: "Arabic (Morocco)", 21 | ar_OM: "Arabic (Oman)", 22 | ar_QA: "Arabic (Qatar)", 23 | ar_SA: "Arabic (Saudi Arabia)", 24 | ar_SD: "Arabic (Sudan)", 25 | ar_SY: "Arabic (Syria)", 26 | ar_TN: "Arabic (Tunisia)", 27 | ar_AE: "Arabic (United Arab Emirates)", 28 | ar_YE: "Arabic (Yemen)", 29 | hy: "Armenian", 30 | hy_AM: "Armenian (Armenia)", 31 | as_IN: "Assamese (India)", 32 | as: "Assamese", 33 | asa: "Asu", 34 | asa_TZ: "Asu (Tanzania)", 35 | az: "Azerbaijani", 36 | az_Cyrl: "Azerbaijani (Cyrillic)", 37 | az_Cyrl_AZ: "Azerbaijani (Cyrillic, Azerbaijan)", 38 | az_Latn: "Azerbaijani (Latin)", 39 | az_Latn_AZ: "Azerbaijani (Latin, Azerbaijan)", 40 | bm: "Bambara", 41 | bm_ML: "Bambara (Mali)", 42 | eu: "Basque", 43 | eu_ES: "Basque (Spain)", 44 | be: "Belarusian", 45 | be_BY: "Belarusian (Belarus)", 46 | bem: "Bemba", 47 | bem_ZM: "Bemba (Zambia)", 48 | bez: "Bena", 49 | bez_TZ: "Bena (Tanzania)", 50 | bn: "Bengali", 51 | bn_BD: "Bengali (Bangladesh)", 52 | bn_IN: "Bengali (India)", 53 | bs_BA: "Bosnian (Bosnia and Herzegovina)", 54 | bs: "Bosnian", 55 | bg: "Bulgarian", 56 | bg_BG: "Bulgarian (Bulgaria)", 57 | my: "Burmese", 58 | my_MM: "Burmese (Myanmar [Burma])", 59 | yue_Hant_HK: "Cantonese (Traditional, Hong Kong SAR China)", 60 | ca: "Catalan", 61 | ca_ES: "Catalan (Spain)", 62 | tzm: "Central Morocco Tamazight", 63 | tzm_Latn: "Central Morocco Tamazight (Latin)", 64 | tzm_Latn_MA: "Central Morocco Tamazight (Latin, Morocco)", 65 | chr: "Cherokee", 66 | chr_US: "Cherokee (United States)", 67 | cgg: "Chiga", 68 | cgg_UG: "Chiga (Uganda)", 69 | zh: "Chinese", 70 | zh_Hans: "Chinese (Simplified Han)", 71 | zh_Hans_CN: "Chinese (Simplified Han, China)", 72 | zh_Hans_HK: "Chinese (Simplified Han, Hong Kong SAR China)", 73 | zh_Hans_MO: "Chinese (Simplified Han, Macau SAR China)", 74 | zh_Hans_SG: "Chinese (Simplified Han, Singapore)", 75 | zh_Hant: "Chinese (Traditional Han)", 76 | zh_Hant_HK: "Chinese (Traditional Han, Hong Kong SAR China)", 77 | zh_Hant_MO: "Chinese (Traditional Han, Macau SAR China)", 78 | zh_Hant_TW: "Chinese (Traditional Han, Taiwan)", 79 | kw: "Cornish", 80 | kw_GB: "Cornish (United Kingdom)", 81 | hr: "Croatian", 82 | hr_HR: "Croatian (Croatia)", 83 | cs: "Czech", 84 | cs_CZ: "Czech (Czech Republic)", 85 | da: "Danish", 86 | da_DK: "Danish (Denmark)", 87 | nl: "Dutch", 88 | nl_BE: "Dutch (Belgium)", 89 | nl_NL: "Dutch (Netherlands)", 90 | ebu: "Embu", 91 | ebu_KE: "Embu (Kenya)", 92 | en: "English", 93 | en_AS: "English (American Samoa)", 94 | en_AU: "English (Australia)", 95 | en_BE: "English (Belgium)", 96 | en_BZ: "English (Belize)", 97 | en_BW: "English (Botswana)", 98 | en_CA: "English (Canada)", 99 | en_GU: "English (Guam)", 100 | en_HK: "English (Hong Kong SAR China)", 101 | en_IN: "English (India)", 102 | en_IE: "English (Ireland)", 103 | en_IL: "English (Israel)", 104 | en_JM: "English (Jamaica)", 105 | en_MT: "English (Malta)", 106 | en_MH: "English (Marshall Islands)", 107 | en_MU: "English (Mauritius)", 108 | en_NA: "English (Namibia)", 109 | en_NZ: "English (New Zealand)", 110 | en_MP: "English (Northern Mariana Islands)", 111 | en_PK: "English (Pakistan)", 112 | en_PH: "English (Philippines)", 113 | en_SG: "English (Singapore)", 114 | en_ZA: "English (South Africa)", 115 | en_TT: "English (Trinidad and Tobago)", 116 | en_UM: "English (U.S. Minor Outlying Islands)", 117 | en_VI: "English (U.S. Virgin Islands)", 118 | en_GB: "English (United Kingdom)", 119 | en_US: "English (United States)", 120 | en_ZW: "English (Zimbabwe)", 121 | eo: "Esperanto", 122 | et: "Estonian", 123 | et_EE: "Estonian (Estonia)", 124 | ee: "Ewe", 125 | ee_GH: "Ewe (Ghana)", 126 | ee_TG: "Ewe (Togo)", 127 | fo: "Faroese", 128 | fo_FO: "Faroese (Faroe Islands)", 129 | fil: "Filipino", 130 | fil_PH: "Filipino (Philippines)", 131 | fi: "Finnish", 132 | fi_FI: "Finnish (Finland)", 133 | fr: "French", 134 | fr_BE: "French (Belgium)", 135 | fr_BJ: "French (Benin)", 136 | fr_BF: "French (Burkina Faso)", 137 | fr_BI: "French (Burundi)", 138 | fr_CM: "French (Cameroon)", 139 | fr_CA: "French (Canada)", 140 | fr_CF: "French (Central African Republic)", 141 | fr_TD: "French (Chad)", 142 | fr_KM: "French (Comoros)", 143 | fr_CG: "French (Congo - Brazzaville)", 144 | fr_CD: "French (Congo - Kinshasa)", 145 | fr_CI: "French (Côte d’Ivoire)", 146 | fr_DJ: "French (Djibouti)", 147 | fr_GQ: "French (Equatorial Guinea)", 148 | fr_FR: "French (France)", 149 | fr_GA: "French (Gabon)", 150 | fr_GP: "French (Guadeloupe)", 151 | fr_GN: "French (Guinea)", 152 | fr_LU: "French (Luxembourg)", 153 | fr_MG: "French (Madagascar)", 154 | fr_ML: "French (Mali)", 155 | fr_MQ: "French (Martinique)", 156 | fr_MC: "French (Monaco)", 157 | fr_NE: "French (Niger)", 158 | fr_RW: "French (Rwanda)", 159 | fr_RE: "French (Réunion)", 160 | fr_BL: "French (Saint Barthélemy)", 161 | fr_MF: "French (Saint Martin)", 162 | fr_SN: "French (Senegal)", 163 | fr_CH: "French (Switzerland)", 164 | fr_TG: "French (Togo)", 165 | ff: "Fulah", 166 | ff_SN: "Fulah (Senegal)", 167 | gl: "Galician", 168 | gl_ES: "Galician (Spain)", 169 | lg: "Ganda", 170 | lg_UG: "Ganda (Uganda)", 171 | ka: "Georgian", 172 | ka_GE: "Georgian (Georgia)", 173 | de: "German", 174 | de_AT: "German (Austria)", 175 | de_BE: "German (Belgium)", 176 | de_DE: "German (Germany)", 177 | de_LI: "German (Liechtenstein)", 178 | de_LU: "German (Luxembourg)", 179 | de_CH: "German (Switzerland)", 180 | el: "Greek", 181 | el_CY: "Greek (Cyprus)", 182 | el_GR: "Greek (Greece)", 183 | gu: "Gujarati", 184 | gu_IN: "Gujarati (India)", 185 | guz: "Gusii", 186 | guz_KE: "Gusii (Kenya)", 187 | ha: "Hausa", 188 | ha_Latn: "Hausa (Latin)", 189 | ha_Latn_GH: "Hausa (Latin, Ghana)", 190 | ha_Latn_NE: "Hausa (Latin, Niger)", 191 | ha_Latn_NG: "Hausa (Latin, Nigeria)", 192 | haw: "Hawaiian", 193 | haw_US: "Hawaiian (United States)", 194 | he: "Hebrew", 195 | he_IL: "Hebrew (Israel)", 196 | hi: "Hindi", 197 | hi_IN: "Hindi (India)", 198 | hu: "Hungarian", 199 | hu_HU: "Hungarian (Hungary)", 200 | is: "Icelandic", 201 | is_IS: "Icelandic (Iceland)", 202 | ig: "Igbo", 203 | ig_NG: "Igbo (Nigeria)", 204 | id: "Indonesian", 205 | id_ID: "Indonesian (Indonesia)", 206 | ga: "Irish", 207 | ga_IE: "Irish (Ireland)", 208 | it: "Italian", 209 | it_IT: "Italian (Italy)", 210 | it_CH: "Italian (Switzerland)", 211 | ja: "Japanese", 212 | ja_JP: "Japanese (Japan)", 213 | kea: "Kabuverdianu", 214 | kea_CV: "Kabuverdianu (Cape Verde)", 215 | kab: "Kabyle", 216 | kab_DZ: "Kabyle (Algeria)", 217 | kl_GL: "Kalaallisut (Greenland)", 218 | kl: "Kalaallisut", 219 | kln: "Kalenjin", 220 | kln_KE: "Kalenjin (Kenya)", 221 | kam: "Kamba", 222 | kam_KE: "Kamba (Kenya)", 223 | kn: "Kannada", 224 | kn_IN: "Kannada (India)", 225 | kk: "Kazakh", 226 | kk_Cyrl: "Kazakh (Cyrillic)", 227 | kk_Cyrl_KZ: "Kazakh (Cyrillic, Kazakhstan)", 228 | km: "Khmer", 229 | km_KH: "Khmer (Cambodia)", 230 | ki: "Kikuyu", 231 | ki_KE: "Kikuyu (Kenya)", 232 | rw: "Kinyarwanda", 233 | rw_RW: "Kinyarwanda (Rwanda)", 234 | kok: "Konkani", 235 | kok_IN: "Konkani (India)", 236 | ko: "Korean", 237 | ko_KR: "Korean (South Korea)", 238 | khq: "Koyra Chiini", 239 | khq_ML: "Koyra Chiini (Mali)", 240 | ses: "Koyraboro Senni", 241 | ses_ML: "Koyraboro Senni (Mali)", 242 | lag: "Langi", 243 | lag_TZ: "Langi (Tanzania)", 244 | lv: "Latvian", 245 | lv_LV: "Latvian (Latvia)", 246 | lt: "Lithuanian", 247 | lt_LT: "Lithuanian (Lithuania)", 248 | luo: "Luo", 249 | luo_KE: "Luo (Kenya)", 250 | luy: "Luyia", 251 | luy_KE: "Luyia (Kenya)", 252 | mk: "Macedonian", 253 | mk_MK: "Macedonian (Macedonia)", 254 | jmc: "Machame", 255 | jmc_TZ: "Machame (Tanzania)", 256 | kde: "Makonde", 257 | kde_TZ: "Makonde (Tanzania)", 258 | mg: "Malagasy", 259 | mg_MG: "Malagasy (Madagascar)", 260 | ms: "Malay", 261 | ms_BN: "Malay (Brunei)", 262 | ms_MY: "Malay (Malaysia)", 263 | ml: "Malayalam", 264 | ml_IN: "Malayalam (India)", 265 | mt: "Maltese", 266 | mt_MT: "Maltese (Malta)", 267 | gv: "Manx", 268 | gv_GB: "Manx (United Kingdom)", 269 | mr: "Marathi", 270 | mr_IN: "Marathi (India)", 271 | mas: "Masai", 272 | mas_KE: "Masai (Kenya)", 273 | mas_TZ: "Masai (Tanzania)", 274 | mer: "Meru", 275 | mer_KE: "Meru (Kenya)", 276 | mfe: "Morisyen", 277 | mfe_MU: "Morisyen (Mauritius)", 278 | naq: "Nama", 279 | naq_NA: "Nama (Namibia)", 280 | ne: "Nepali", 281 | ne_IN: "Nepali (India)", 282 | ne_NP: "Nepali (Nepal)", 283 | nd: "North Ndebele", 284 | nd_ZW: "North Ndebele (Zimbabwe)", 285 | nb: "Norwegian Bokmål", 286 | nb_NO: "Norwegian Bokmål (Norway)", 287 | nn: "Norwegian Nynorsk", 288 | nn_NO: "Norwegian Nynorsk (Norway)", 289 | nyn: "Nyankole", 290 | nyn_UG: "Nyankole (Uganda)", 291 | or: "Oriya", 292 | or_IN: "Oriya (India)", 293 | om: "Oromo", 294 | om_ET: "Oromo (Ethiopia)", 295 | om_KE: "Oromo (Kenya)", 296 | ps: "Pashto", 297 | ps_AF: "Pashto (Afghanistan)", 298 | fa: "Persian", 299 | fa_AF: "Persian (Afghanistan)", 300 | fa_IR: "Persian (Iran)", 301 | pl_PL: "Polish (Poland)", 302 | pl: "Polish", 303 | pt: "Portuguese", 304 | pt_BR: "Portuguese (Brazil)", 305 | pt_GW: "Portuguese (Guinea-Bissau)", 306 | pt_MZ: "Portuguese (Mozambique)", 307 | pt_PT: "Portuguese (Portugal)", 308 | pa: "Punjabi", 309 | pa_Arab: "Punjabi (Arabic)", 310 | pa_Arab_PK: "Punjabi (Arabic, Pakistan)", 311 | pa_Guru: "Punjabi (Gurmukhi)", 312 | pa_Guru_IN: "Punjabi (Gurmukhi, India)", 313 | ro: "Romanian", 314 | ro_MD: "Romanian (Moldova)", 315 | ro_RO: "Romanian (Romania)", 316 | rm_CH: "Romansh (Switzerland)", 317 | rm: "Romansh", 318 | rof: "Rombo", 319 | rof_TZ: "Rombo (Tanzania)", 320 | ru: "Russian", 321 | ru_MD: "Russian (Moldova)", 322 | ru_RU: "Russian (Russia)", 323 | ru_UA: "Russian (Ukraine)", 324 | rwk: "Rwa", 325 | rwk_TZ: "Rwa (Tanzania)", 326 | saq: "Samburu", 327 | saq_KE: "Samburu (Kenya)", 328 | sg: "Sango", 329 | sg_CF: "Sango (Central African Republic)", 330 | seh: "Sena", 331 | seh_MZ: "Sena (Mozambique)", 332 | sr: "Serbian", 333 | sr_Cyrl: "Serbian (Cyrillic)", 334 | sr_Cyrl_BA: "Serbian (Cyrillic, Bosnia and Herzegovina)", 335 | sr_Cyrl_ME: "Serbian (Cyrillic, Montenegro)", 336 | sr_Cyrl_RS: "Serbian (Cyrillic, Serbia)", 337 | sr_Latn: "Serbian (Latin)", 338 | sr_Latn_BA: "Serbian (Latin, Bosnia and Herzegovina)", 339 | sr_Latn_ME: "Serbian (Latin, Montenegro)", 340 | sr_Latn_RS: "Serbian (Latin, Serbia)", 341 | sn: "Shona", 342 | sn_ZW: "Shona (Zimbabwe)", 343 | ii: "Sichuan Yi", 344 | ii_CN: "Sichuan Yi (China)", 345 | si: "Sinhala", 346 | si_LK: "Sinhala (Sri Lanka)", 347 | sk: "Slovak", 348 | sk_SK: "Slovak (Slovakia)", 349 | sl: "Slovenian", 350 | sl_SI: "Slovenian (Slovenia)", 351 | xog: "Soga", 352 | xog_UG: "Soga (Uganda)", 353 | so: "Somali", 354 | so_DJ: "Somali (Djibouti)", 355 | so_ET: "Somali (Ethiopia)", 356 | so_KE: "Somali (Kenya)", 357 | so_SO: "Somali (Somalia)", 358 | es: "Spanish", 359 | es_AR: "Spanish (Argentina)", 360 | es_BO: "Spanish (Bolivia)", 361 | es_CL: "Spanish (Chile)", 362 | es_CO: "Spanish (Colombia)", 363 | es_CR: "Spanish (Costa Rica)", 364 | es_DO: "Spanish (Dominican Republic)", 365 | es_EC: "Spanish (Ecuador)", 366 | es_SV: "Spanish (El Salvador)", 367 | es_GQ: "Spanish (Equatorial Guinea)", 368 | es_GT: "Spanish (Guatemala)", 369 | es_HN: "Spanish (Honduras)", 370 | es_419: "Spanish (Latin America)", 371 | es_MX: "Spanish (Mexico)", 372 | es_NI: "Spanish (Nicaragua)", 373 | es_PA: "Spanish (Panama)", 374 | es_PY: "Spanish (Paraguay)", 375 | es_PE: "Spanish (Peru)", 376 | es_PR: "Spanish (Puerto Rico)", 377 | es_ES: "Spanish (Spain)", 378 | es_US: "Spanish (United States)", 379 | es_UY: "Spanish (Uruguay)", 380 | es_VE: "Spanish (Venezuela)", 381 | sw: "Swahili", 382 | sw_KE: "Swahili (Kenya)", 383 | sw_TZ: "Swahili (Tanzania)", 384 | sv: "Swedish", 385 | sv_FI: "Swedish (Finland)", 386 | sv_SE: "Swedish (Sweden)", 387 | gsw: "Swiss German", 388 | gsw_CH: "Swiss German (Switzerland)", 389 | shi: "Tachelhit", 390 | shi_Latn: "Tachelhit (Latin)", 391 | shi_Latn_MA: "Tachelhit (Latin, Morocco)", 392 | shi_Tfng: "Tachelhit (Tifinagh)", 393 | shi_Tfng_MA: "Tachelhit (Tifinagh, Morocco)", 394 | dav: "Taita", 395 | dav_KE: "Taita (Kenya)", 396 | ta: "Tamil", 397 | ta_IN: "Tamil (India)", 398 | ta_LK: "Tamil (Sri Lanka)", 399 | te: "Telugu", 400 | te_IN: "Telugu (India)", 401 | teo: "Teso", 402 | teo_KE: "Teso (Kenya)", 403 | teo_UG: "Teso (Uganda)", 404 | th: "Thai", 405 | th_TH: "Thai (Thailand)", 406 | bo: "Tibetan", 407 | bo_CN: "Tibetan (China)", 408 | bo_IN: "Tibetan (India)", 409 | ti: "Tigrinya", 410 | ti_ER: "Tigrinya (Eritrea)", 411 | ti_ET: "Tigrinya (Ethiopia)", 412 | to: "Tonga", 413 | to_TO: "Tonga (Tonga)", 414 | tr: "Turkish", 415 | tr_TR: "Turkish (Turkey)", 416 | uk: "Ukrainian", 417 | uk_UA: "Ukrainian (Ukraine)", 418 | ur: "Urdu", 419 | ur_IN: "Urdu (India)", 420 | ur_PK: "Urdu (Pakistan)", 421 | uz: "Uzbek", 422 | uz_Arab: "Uzbek (Arabic)", 423 | uz_Arab_AF: "Uzbek (Arabic, Afghanistan)", 424 | uz_Cyrl: "Uzbek (Cyrillic)", 425 | uz_Cyrl_UZ: "Uzbek (Cyrillic, Uzbekistan)", 426 | uz_Latn: "Uzbek (Latin)", 427 | uz_Latn_UZ: "Uzbek (Latin, Uzbekistan)", 428 | vi: "Vietnamese", 429 | vi_VN: "Vietnamese (Vietnam)", 430 | vun: "Vunjo", 431 | vun_TZ: "Vunjo (Tanzania)", 432 | cy: "Welsh", 433 | cy_GB: "Welsh (United Kingdom)", 434 | yo: "Yoruba", 435 | yo_NG: "Yoruba (Nigeria)", 436 | zu: "Zulu", 437 | zu_ZA: "Zulu (South Africa)" 438 | } -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "quasalang", 3 | "version": "1.1.1", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "ansi-escapes": { 8 | "version": "4.3.1", 9 | "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.1.tgz", 10 | "integrity": "sha512-JWF7ocqNrp8u9oqpgV+wH5ftbt+cfvv+PTjOvKLT3AdYly/LmORARfEVT1iyjwN+4MqE5UmVKoAdIBqeoCHgLA==", 11 | "requires": { 12 | "type-fest": "^0.11.0" 13 | } 14 | }, 15 | "ansi-regex": { 16 | "version": "5.0.0", 17 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", 18 | "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==" 19 | }, 20 | "ansi-styles": { 21 | "version": "4.3.0", 22 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", 23 | "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", 24 | "requires": { 25 | "color-convert": "^2.0.1" 26 | } 27 | }, 28 | "balanced-match": { 29 | "version": "1.0.0", 30 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", 31 | "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" 32 | }, 33 | "brace-expansion": { 34 | "version": "1.1.11", 35 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", 36 | "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", 37 | "requires": { 38 | "balanced-match": "^1.0.0", 39 | "concat-map": "0.0.1" 40 | } 41 | }, 42 | "chalk": { 43 | "version": "4.1.0", 44 | "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", 45 | "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", 46 | "requires": { 47 | "ansi-styles": "^4.1.0", 48 | "supports-color": "^7.1.0" 49 | } 50 | }, 51 | "chardet": { 52 | "version": "0.7.0", 53 | "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", 54 | "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==" 55 | }, 56 | "cli-cursor": { 57 | "version": "3.1.0", 58 | "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", 59 | "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", 60 | "requires": { 61 | "restore-cursor": "^3.1.0" 62 | } 63 | }, 64 | "cli-width": { 65 | "version": "3.0.0", 66 | "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-3.0.0.tgz", 67 | "integrity": "sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==" 68 | }, 69 | "color-convert": { 70 | "version": "2.0.1", 71 | "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", 72 | "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", 73 | "requires": { 74 | "color-name": "~1.1.4" 75 | } 76 | }, 77 | "color-name": { 78 | "version": "1.1.4", 79 | "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", 80 | "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" 81 | }, 82 | "colors": { 83 | "version": "1.4.0", 84 | "resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz", 85 | "integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==" 86 | }, 87 | "commander": { 88 | "version": "7.2.0", 89 | "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", 90 | "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==" 91 | }, 92 | "concat-map": { 93 | "version": "0.0.1", 94 | "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", 95 | "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" 96 | }, 97 | "csv-parser": { 98 | "version": "3.0.0", 99 | "resolved": "https://registry.npmjs.org/csv-parser/-/csv-parser-3.0.0.tgz", 100 | "integrity": "sha512-s6OYSXAK3IdKqYO33y09jhypG/bSDHPuyCme/IdEHfWpLf/jKcpitVFyOC6UemgGk8v7Q5u2XE0vvwmanxhGlQ==", 101 | "requires": { 102 | "minimist": "^1.2.0" 103 | } 104 | }, 105 | "emoji-regex": { 106 | "version": "8.0.0", 107 | "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", 108 | "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" 109 | }, 110 | "escape-string-regexp": { 111 | "version": "1.0.5", 112 | "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", 113 | "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" 114 | }, 115 | "external-editor": { 116 | "version": "3.1.0", 117 | "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz", 118 | "integrity": "sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==", 119 | "requires": { 120 | "chardet": "^0.7.0", 121 | "iconv-lite": "^0.4.24", 122 | "tmp": "^0.0.33" 123 | } 124 | }, 125 | "figlet": { 126 | "version": "1.5.0", 127 | "resolved": "https://registry.npmjs.org/figlet/-/figlet-1.5.0.tgz", 128 | "integrity": "sha512-ZQJM4aifMpz6H19AW1VqvZ7l4pOE9p7i/3LyxgO2kp+PO/VcDYNqIHEMtkccqIhTXMKci4kjueJr/iCQEaT/Ww==" 129 | }, 130 | "figures": { 131 | "version": "3.2.0", 132 | "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", 133 | "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==", 134 | "requires": { 135 | "escape-string-regexp": "^1.0.5" 136 | } 137 | }, 138 | "fs.realpath": { 139 | "version": "1.0.0", 140 | "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", 141 | "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" 142 | }, 143 | "glob": { 144 | "version": "7.1.6", 145 | "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", 146 | "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", 147 | "requires": { 148 | "fs.realpath": "^1.0.0", 149 | "inflight": "^1.0.4", 150 | "inherits": "2", 151 | "minimatch": "^3.0.4", 152 | "once": "^1.3.0", 153 | "path-is-absolute": "^1.0.0" 154 | } 155 | }, 156 | "has-flag": { 157 | "version": "4.0.0", 158 | "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", 159 | "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" 160 | }, 161 | "iconv-lite": { 162 | "version": "0.4.24", 163 | "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", 164 | "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", 165 | "requires": { 166 | "safer-buffer": ">= 2.1.2 < 3" 167 | } 168 | }, 169 | "inflight": { 170 | "version": "1.0.6", 171 | "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", 172 | "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", 173 | "requires": { 174 | "once": "^1.3.0", 175 | "wrappy": "1" 176 | } 177 | }, 178 | "inherits": { 179 | "version": "2.0.4", 180 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", 181 | "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" 182 | }, 183 | "inquirer": { 184 | "version": "8.0.0", 185 | "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-8.0.0.tgz", 186 | "integrity": "sha512-ON8pEJPPCdyjxj+cxsYRe6XfCJepTxANdNnTebsTuQgXpRyZRRT9t4dJwjRubgmvn20CLSEnozRUayXyM9VTXA==", 187 | "requires": { 188 | "ansi-escapes": "^4.2.1", 189 | "chalk": "^4.1.0", 190 | "cli-cursor": "^3.1.0", 191 | "cli-width": "^3.0.0", 192 | "external-editor": "^3.0.3", 193 | "figures": "^3.0.0", 194 | "lodash": "^4.17.21", 195 | "mute-stream": "0.0.8", 196 | "run-async": "^2.4.0", 197 | "rxjs": "^6.6.6", 198 | "string-width": "^4.1.0", 199 | "strip-ansi": "^6.0.0", 200 | "through": "^2.3.6" 201 | } 202 | }, 203 | "is-fullwidth-code-point": { 204 | "version": "3.0.0", 205 | "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", 206 | "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==" 207 | }, 208 | "lodash": { 209 | "version": "4.17.21", 210 | "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", 211 | "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" 212 | }, 213 | "mimic-fn": { 214 | "version": "2.1.0", 215 | "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", 216 | "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==" 217 | }, 218 | "minimatch": { 219 | "version": "3.0.4", 220 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", 221 | "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", 222 | "requires": { 223 | "brace-expansion": "^1.1.7" 224 | } 225 | }, 226 | "minimist": { 227 | "version": "1.2.5", 228 | "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", 229 | "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" 230 | }, 231 | "mute-stream": { 232 | "version": "0.0.8", 233 | "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", 234 | "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==" 235 | }, 236 | "once": { 237 | "version": "1.4.0", 238 | "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", 239 | "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", 240 | "requires": { 241 | "wrappy": "1" 242 | } 243 | }, 244 | "onetime": { 245 | "version": "5.1.2", 246 | "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", 247 | "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", 248 | "requires": { 249 | "mimic-fn": "^2.1.0" 250 | } 251 | }, 252 | "os-tmpdir": { 253 | "version": "1.0.2", 254 | "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", 255 | "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=" 256 | }, 257 | "path-is-absolute": { 258 | "version": "1.0.1", 259 | "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", 260 | "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" 261 | }, 262 | "restore-cursor": { 263 | "version": "3.1.0", 264 | "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", 265 | "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", 266 | "requires": { 267 | "onetime": "^5.1.0", 268 | "signal-exit": "^3.0.2" 269 | } 270 | }, 271 | "rimraf": { 272 | "version": "3.0.2", 273 | "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", 274 | "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", 275 | "requires": { 276 | "glob": "^7.1.3" 277 | } 278 | }, 279 | "run-async": { 280 | "version": "2.4.1", 281 | "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz", 282 | "integrity": "sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==" 283 | }, 284 | "rxjs": { 285 | "version": "6.6.6", 286 | "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.6.tgz", 287 | "integrity": "sha512-/oTwee4N4iWzAMAL9xdGKjkEHmIwupR3oXbQjCKywF1BeFohswF3vZdogbmEF6pZkOsXTzWkrZszrWpQTByYVg==", 288 | "requires": { 289 | "tslib": "^1.9.0" 290 | } 291 | }, 292 | "safer-buffer": { 293 | "version": "2.1.2", 294 | "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", 295 | "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" 296 | }, 297 | "signal-exit": { 298 | "version": "3.0.3", 299 | "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz", 300 | "integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==" 301 | }, 302 | "string-width": { 303 | "version": "4.2.2", 304 | "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.2.tgz", 305 | "integrity": "sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==", 306 | "requires": { 307 | "emoji-regex": "^8.0.0", 308 | "is-fullwidth-code-point": "^3.0.0", 309 | "strip-ansi": "^6.0.0" 310 | } 311 | }, 312 | "strip-ansi": { 313 | "version": "6.0.0", 314 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", 315 | "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", 316 | "requires": { 317 | "ansi-regex": "^5.0.0" 318 | } 319 | }, 320 | "supports-color": { 321 | "version": "7.2.0", 322 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", 323 | "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", 324 | "requires": { 325 | "has-flag": "^4.0.0" 326 | } 327 | }, 328 | "through": { 329 | "version": "2.3.8", 330 | "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", 331 | "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=" 332 | }, 333 | "tmp": { 334 | "version": "0.0.33", 335 | "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", 336 | "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", 337 | "requires": { 338 | "os-tmpdir": "~1.0.2" 339 | } 340 | }, 341 | "tslib": { 342 | "version": "1.14.1", 343 | "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", 344 | "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" 345 | }, 346 | "type-fest": { 347 | "version": "0.11.0", 348 | "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.11.0.tgz", 349 | "integrity": "sha512-OdjXJxnCN1AvyLSzeKIgXTXxV+99ZuXl3Hpo9XpJAv9MBcHrrJOQ5kV7ypXOuQie+AmWG25hLbiKdwYTifzcfQ==" 350 | }, 351 | "wrappy": { 352 | "version": "1.0.2", 353 | "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", 354 | "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" 355 | } 356 | } 357 | } 358 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "quasalang", 3 | "version": "1.1.1", 4 | "description": "Generate Quasar i18n language files from a CSV file", 5 | "main": "index.js", 6 | "preferGlobal": true, 7 | "bin": "./commands.js", 8 | "scripts": { 9 | "test": "echo \"Error: no test specified\" && exit 1" 10 | }, 11 | "author": "Danny Connell", 12 | "license": "MIT", 13 | "dependencies": { 14 | "colors": "^1.4.0", 15 | "commander": "^7.2.0", 16 | "csv-parser": "^3.0.0", 17 | "figlet": "^1.5.0", 18 | "inquirer": "^8.0.0", 19 | "rimraf": "^3.0.2" 20 | }, 21 | "repository": { 22 | "type": "git", 23 | "url": "git+https://github.com/dannyconnell/quasalang.git" 24 | }, 25 | "keywords": [ 26 | "quasar", 27 | "quasar framework", 28 | "i18n", 29 | "csv" 30 | ], 31 | "bugs": { 32 | "url": "https://github.com/dannyconnell/quasalang/issues" 33 | }, 34 | "homepage": "https://github.com/dannyconnell/quasalang#readme" 35 | } 36 | -------------------------------------------------------------------------------- /sample-csv/translations.csv: -------------------------------------------------------------------------------- 1 | Key,"English, en-US","French, fr","German, de" 2 | hello,"Hello","Bonjour","Hallo" 3 | goodbye,"Goodbye","Au revoir","Auf Wiedersehen" 4 | thanks,"Thanks","Merci","Danke" -------------------------------------------------------------------------------- /utils/getLanguagesAndCodesAsObjects.js: -------------------------------------------------------------------------------- 1 | module.exports = function() { 2 | this.getLanguagesAndCodesAsObjects = function(results) { 3 | // get first row that has data 4 | let firstRowWithData = {} 5 | for (let i = 0; i < results.length; i++) { 6 | const result = results[i] 7 | if (result.Key && !result.Key.startsWith('#')) { 8 | firstRowWithData = result 9 | break; 10 | } 11 | } 12 | 13 | // get languages and codes 14 | let languagesAndCodes = Object.assign({}, firstRowWithData) 15 | delete languagesAndCodes['Key'] 16 | languagesAndCodes = Object.keys(languagesAndCodes) 17 | 18 | // generate array of lang, code & codeAsVariable as objects 19 | let languagesAndCodesAsObjects = [] 20 | 21 | languagesAndCodes.forEach(languageAndCode => { 22 | let langAndCode = languageAndCode 23 | let lang = languageAndCode.split(',')[0] 24 | let code = languageAndCode.split(',')[1].trim() 25 | let codeAsVariable = code.split('-').join('') 26 | let languagesAndCodesObject = { 27 | langAndCode, 28 | lang, 29 | code, 30 | codeAsVariable 31 | } 32 | languagesAndCodesAsObjects.push(languagesAndCodesObject) 33 | 34 | }); 35 | 36 | return languagesAndCodesAsObjects 37 | } 38 | } --------------------------------------------------------------------------------