i18n.js 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159
  1. 'use strict';
  2. const gutil = require('gulp-util');
  3. const through = require('through2');
  4. const fs = require('fs');
  5. function Locale(lang, path, base) {
  6. this.lang = lang;
  7. this.path = path;
  8. this.base = base || '.';
  9. this.data = {};
  10. this.loaded = this.read();
  11. }
  12. Locale.prototype.read = function () {
  13. return new Promise((resolve, reject) => {
  14. const file = this.base + '/' + this.path;
  15. fs.readFile(file, 'utf8', (err, data) => err ? reject(err) : resolve(data));
  16. }).then((data) => {
  17. const desc = {};
  18. data = JSON.parse(data);
  19. for (let key in data) {
  20. this.data[key] = data[key].message;
  21. desc[key] = data[key].description;
  22. }
  23. return desc;
  24. });
  25. };
  26. Locale.prototype.get = function (key, def) {
  27. return this.data[key] || def;
  28. };
  29. function Locales(prefix, base) {
  30. this.prefix = prefix || '.';
  31. this.base = base || '.';
  32. this.langs = [];
  33. this.data = {};
  34. this.desc = {};
  35. this.loaded = this.load();
  36. }
  37. Locales.prototype.defaultLang = 'en';
  38. Locales.prototype.newLocaleItem = 'NEW_LOCALE_ITEM';
  39. Locales.prototype.getLanguages = function () {
  40. const localeDir = this.base + '/' + this.prefix;
  41. return new Promise((resolve, reject) => {
  42. fs.readdir(localeDir, (err, files) => err ? reject(err) : resolve(files));
  43. });
  44. };
  45. Locales.prototype.load = function () {
  46. return this.getLanguages().then((langs) => {
  47. this.langs = langs;
  48. return Promise.all(langs.map((lang) => {
  49. const locale = this.data[lang] = new Locale(lang, `${this.prefix}/${lang}/messages.json`, this.base);
  50. return locale.loaded;
  51. }));
  52. }).then((data) => {
  53. const desc = data[this.langs.indexOf(this.defaultLang)];
  54. for (let key in desc) {
  55. this.desc[key] = {
  56. touched: false,
  57. value: desc[key],
  58. };
  59. }
  60. });
  61. };
  62. Locales.prototype.getData = function (lang, options) {
  63. options = options || {};
  64. const data = {};
  65. const langData = this.data[lang];
  66. const defaultData = options.useDefaultLang && lang != this.defaultLang && this.data[this.defaultLang];
  67. for (let key in this.desc) {
  68. if (options.touchedOnly && !this.desc[key].touched) continue;
  69. data[key] = {
  70. description: this.desc[key].value || this.newLocaleItem,
  71. message: langData.get(key) || defaultData && defaultData.get(key) || '',
  72. };
  73. if (options.markUntouched && !this.desc[key].touched)
  74. data[key].touched = false;
  75. }
  76. return data;
  77. };
  78. Locales.prototype.dump = function (options) {
  79. return this.langs.map((lang) => {
  80. const data = this.getData(lang, options);
  81. const string = JSON.stringify(data, null, 2);
  82. return new gutil.File({
  83. base: '',
  84. path: this.data[lang].path,
  85. contents: new Buffer(string),
  86. });
  87. });
  88. };
  89. Locales.prototype.touch = function (key) {
  90. let item = this.desc[key];
  91. if (!item) item = this.desc[key] = {
  92. value: this.newLocaleItem,
  93. };
  94. item.touched = true;
  95. };
  96. function extract(options) {
  97. const keys = new Set();
  98. const patterns = {
  99. js: ['_.i18n\\(([\'"])(\\w+)\\1', 2],
  100. json: ['__MSG_(\\w+)__', 1],
  101. html: ['data-i18n=([\'"]?)(\\w+)\\1', 2],
  102. };
  103. const locales = new Locales(options.prefix, options.base);
  104. function extract(data, types) {
  105. if (!Array.isArray(types)) types = [types];
  106. data = String(data);
  107. types.forEach(function (type) {
  108. const patternData = patterns[type];
  109. const pattern = new RegExp(patternData[0], 'g');
  110. const groupId = patternData[1];
  111. let groups;
  112. while (groups = pattern.exec(data)) {
  113. keys.add(groups[groupId]);
  114. }
  115. });
  116. }
  117. function bufferContents(file, enc, cb) {
  118. if (file.isNull()) return cb();
  119. if (file.isStream())
  120. return this.emit('error', new gutil.PluginError('VM-i18n', 'Stream is not supported.'));
  121. if (file.path.endsWith('.js'))
  122. extract(file.contents, 'js');
  123. else if (file.path.endsWith('.json'))
  124. extract(file.contents, 'json');
  125. else if (file.path.endsWith('.html'))
  126. extract(file.contents, ['html', 'js']);
  127. cb();
  128. }
  129. function endStream(cb) {
  130. locales.loaded.then(() => {
  131. keys.forEach((key) => {
  132. locales.touch(key);
  133. });
  134. return locales.dump({
  135. touchedOnly: options.touchedOnly,
  136. useDefaultLang: options.useDefaultLang,
  137. });
  138. }).then((files) => {
  139. files.forEach((file) => {
  140. this.push(file);
  141. });
  142. cb();
  143. });
  144. }
  145. return through.obj(bufferContents, endStream);
  146. }
  147. module.exports = {
  148. extract,
  149. };