├── .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 | }
--------------------------------------------------------------------------------