| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209 | 
							- const fs = require('fs').promises;
 
- const path = require('path');
 
- const Vinyl = require('vinyl');
 
- const PluginError = require('plugin-error');
 
- const through = require('through2');
 
- const yaml = require('js-yaml');
 
- const transformers = {
 
-   '.yml': data => yaml.load(data),
 
-   '.json': data => JSON.parse(data),
 
- };
 
- class Locale {
 
-   constructor(lang, base) {
 
-     this.defaultLocale = 'messages.yml';
 
-     this.lang = lang;
 
-     this.base = base;
 
-     this.data = {};
 
-     this.desc = {};
 
-   }
 
-   async load() {
 
-     const localeDir = `${this.base}/${this.lang}`;
 
-     let files = await fs.readdir(localeDir);
 
-     files = [this.defaultLocale].concat(files.filter(file => file !== this.defaultLocale));
 
-     for (const file of files) {
 
-       const ext = path.extname(file);
 
-       const transformer = transformers[ext];
 
-       if (transformer) {
 
-         try {
 
-           const res = await fs.readFile(`${localeDir}/${file}`, 'utf8');
 
-           Object.assign(this.data, transformer(res));
 
-         } catch (err) {
 
-           if (err.code !== 'ENOENT') {
 
-             throw err;
 
-           }
 
-         }
 
-       }
 
-     }
 
-     Object.keys(this.data)
 
-     .forEach(key => {
 
-       this.desc[key] = this.desc[key] || this.data[key].description;
 
-     });
 
-   }
 
-   getMessage(key, def) {
 
-     const item = this.data[key];
 
-     return item && item.message || def;
 
-   }
 
-   get(key) {
 
-     return this.data[key];
 
-   }
 
-   dump(data, { extension, stripDescriptions }) {
 
-     if (extension === '.json') {
 
-       if (stripDescriptions) {
 
-         data = Object.entries(data).reduce((res, [key, value]) => {
 
-           const { description, ...stripped } = value;
 
-           res[key] = stripped;
 
-           return res;
 
-         }, {});
 
-       }
 
-       data = JSON.stringify(data, null, 2);
 
-     } else if (extension === '.yml') {
 
-       data = yaml.dump(data, { sortKeys: true });
 
-     } else {
 
-       throw 'Unknown extension name!';
 
-     }
 
-     return {
 
-       path: `${this.lang}/messages${extension}`,
 
-       data,
 
-     };
 
-   }
 
- }
 
- class Locales {
 
-   constructor(options) {
 
-     this.options = options;
 
-     this.defaultLang = 'en';
 
-     this.newLocaleItem = 'NEW_LOCALE_ITEM';
 
-     this.base = options.base || '.';
 
-     this.langs = [];
 
-     this.locales = {};
 
-     this.data = {};
 
-   }
 
-   async load() {
 
-     const langs = await fs.readdir(this.base);
 
-     this.langs = langs;
 
-     await Promise.all(langs.map(async lang => {
 
-       const locale = new Locale(lang, this.base);
 
-       await locale.load();
 
-       this.locales[lang] = locale;
 
-     }));
 
-     const defaultData = this.locales[this.defaultLang];
 
-     Object.keys(defaultData.desc).forEach(key => {
 
-       this.data[key] = {
 
-         ...defaultData.data[key],
 
-         touched: this.options.markUntouched ? false : defaultData.get(key).touched !== false,
 
-       };
 
-     });
 
-   }
 
-   getData(lang, options) {
 
-     options = options || {};
 
-     const data = {};
 
-     const langData = this.locales[lang];
 
-     const defaultData = options.useDefaultLang && lang != this.defaultLang && this.locales[this.defaultLang];
 
-     Object.keys(this.data).forEach(key => {
 
-       if (options.touchedOnly && !this.data[key].touched) return;
 
-       data[key] = {
 
-         description: this.data[key].description || this.newLocaleItem,
 
-         message: langData.getMessage(key) || defaultData && defaultData.getMessage(key) || '',
 
-       };
 
-       if (options.markUntouched && !this.data[key].touched) data[key].touched = false;
 
-     });
 
-     return data;
 
-   }
 
-   dump(options) {
 
-     options = { ...this.options, ...options };
 
-     return this.langs.map(lang => {
 
-       const data = this.getData(lang, options);
 
-       const locale = this.locales[lang];
 
-       return locale.dump(data, options);
 
-     });
 
-   }
 
-   touch(key) {
 
-     let item = this.data[key];
 
-     if (!item) item = this.data[key] = {
 
-       description: this.newLocaleItem,
 
-     };
 
-     item.touched = true;
 
-   }
 
- }
 
- function extract(options) {
 
-   const keys = new Set();
 
-   const patterns = {
 
-     default: ['\\b(?:i18n\\(\'|i18n-key=")(\\w+)[\'"]', 1],
 
-     json: ['__MSG_(\\w+)__', 1],
 
-   };
 
-   const types = {
 
-     '.js': 'default',
 
-     '.json': 'json',
 
-     '.html': 'default',
 
-     '.vue': 'default',
 
-   };
 
-   const locales = new Locales(options);
 
-   function extractFile(data, types) {
 
-     if (!Array.isArray(types)) types = [types];
 
-     data = String(data);
 
-     types.forEach(function (type) {
 
-       const patternData = patterns[type];
 
-       const pattern = new RegExp(patternData[0], 'g');
 
-       const groupId = patternData[1];
 
-       let groups;
 
-       while (groups = pattern.exec(data)) {
 
-         keys.add(groups[groupId]);
 
-       }
 
-     });
 
-   }
 
-   function bufferContents(file, enc, cb) {
 
-     if (file.isNull()) return cb();
 
-     if (file.isStream()) return this.emit('error', new PluginError('VM-i18n', 'Stream is not supported.'));
 
-     const extname = path.extname(file.path);
 
-     const type = types[extname];
 
-     if (type) extractFile(file.contents, type);
 
-     cb();
 
-   }
 
-   function endStream(cb) {
 
-     locales.load()
 
-     .then(() => {
 
-       keys.forEach(key => {
 
-         locales.touch(key);
 
-       });
 
-       return locales.dump()
 
-       .map(out => new Vinyl({
 
-         path: out.path,
 
-         contents: Buffer.from(out.data),
 
-       }));
 
-     })
 
-     .then(files => {
 
-       files.forEach(file => {
 
-         this.push(file);
 
-       });
 
-       cb();
 
-     })
 
-     .catch(cb);
 
-   }
 
-   return through.obj(bufferContents, endStream);
 
- }
 
- function read(options) {
 
-   const stream = extract(options);
 
-   process.nextTick(() => stream.end());
 
-   return stream;
 
- }
 
- module.exports = {
 
-   extract,
 
-   read,
 
- };
 
 
  |