i18n.js 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317
  1. var fs = require('fs')
  2. , path = require('path')
  3. , request = require('request')
  4. , async = require('async')
  5. , util = require('util')
  6. ;
  7. var getPotHeader = function() {
  8. var dateFormat = require('dateformat');
  9. var now = dateFormat(new Date(), "yyyy-mm-dd HH:MM+0000", true);
  10. return [
  11. '# Generated pot',
  12. 'msgid ""',
  13. 'msgstr ""',
  14. '"Project-Id-Version: Plupload\\n"',
  15. '"Report-Msgid-Bugs-To: http://moxiecode.com\\n"',
  16. '"POT-Creation-Date: ' + now + '\\n"',
  17. '"PO-Revision-Date: ' + now + '\\n"',
  18. '"MIME-Version: 1.0\\n"',
  19. '"Content-Type: text/plain; charset=UTF-8\\n"',
  20. '"Content-Transfer-Encoding: 8bit\\n"',
  21. '"Last-Translator: Davit Barbakadze <davit@moxiecode.com>\\n"',
  22. '"Language-Team: Support <support@moxiecode.com>\\n"'
  23. ];
  24. };
  25. var extract = function(from, to) {
  26. var methodPtrn = process.env.fn;
  27. var entries = {};
  28. var garbage = [];
  29. from = from || process.env.from;
  30. to = to || process.env.to || './tmp/en.po';
  31. function extract(input) {
  32. var content = fs.readFileSync(path.resolve(input)).toString();
  33. var fileName = path.basename(input);
  34. var regExp;
  35. var m;
  36. var text;
  37. var newLines = [];
  38. function getLine(index) {
  39. var i = newLines.length;
  40. while (i--) {
  41. if (index > newLines[i]) {
  42. return i + 2; // cause it starts from 0 (+1) and we need next to current (+2)
  43. }
  44. }
  45. return 0; // shouldn't happen
  46. }
  47. function trimQuotes(str) {
  48. return str.trim().replace(/^[\'\"]/, '').replace(/[\'\"]$/, '');
  49. }
  50. // find new lines
  51. regExp = /\n/g;
  52. while (m = regExp.exec(content)) {
  53. newLines.push(m.index);
  54. }
  55. regExp = new RegExp("(?:" + (methodPtrn || "plupload\.translate|_") + ")\\(\s*([\\\'\\\"])([\\\s\\\S]*?[^\\\\])\\1", 'g');
  56. while (m = regExp.exec(content)) {
  57. text = trimQuotes(m[2]);
  58. if (garbage.indexOf(text) !== -1) {
  59. continue;
  60. }
  61. if (entries[text]) {
  62. entries[text].comment += ' ' + fileName + ':' + getLine(m.index);
  63. } else {
  64. entries[text] = {
  65. text: trimQuotes(m[2]),
  66. comment: '#: ' + fileName + ':' + getLine(m.index)
  67. };
  68. }
  69. }
  70. newLines = null;
  71. }
  72. function writeToPOT() {
  73. var key, entry;
  74. var lines = getPotHeader();
  75. for (key in entries) {
  76. entry = entries[key];
  77. if (lines.length) {
  78. lines.push('');
  79. }
  80. lines.push(entry.comment);
  81. if (/%[a-z]/.test(entry.text)) {
  82. lines.push('#, c-format');
  83. }
  84. lines.push(
  85. 'msgid "' + entry.text + '"',
  86. 'msgstr ""'
  87. );
  88. }
  89. jake.mkdirP(path.dirname(to));
  90. fs.writeFileSync(to, lines.join('\n'));
  91. console.info(to + " generated.");
  92. }
  93. findFiles(from).forEach(extract);
  94. if (to) {
  95. writeToPOT();
  96. }
  97. return entries;
  98. };
  99. var findFiles = function(filePath, baseDir) {
  100. var files = [];
  101. // If array of paths or path expressions
  102. if (filePath instanceof Array) {
  103. filePath.forEach(function(filePath) {
  104. Array.prototype.push.apply(files, findFiles(filePath, baseDir));
  105. });
  106. return files;
  107. }
  108. filePath = path.join(baseDir || './', filePath);
  109. if (filePath.indexOf('*') != -1) {
  110. // Use glob if whildcard pattern
  111. Array.prototype.push.apply(files, require("glob").sync(filePath));
  112. } else {
  113. // Single file
  114. files.push(filePath);
  115. }
  116. return files;
  117. };
  118. var toPot = function(from, to) {
  119. to = to || process.env.to || './tmp/i18n';
  120. if (fs.existsSync(to)) {
  121. jake.rmRf(to);
  122. }
  123. jake.mkdirP(to);
  124. function generatePOT(input) {
  125. var srcItems, json, lines = [], fileName, name;
  126. input = path.resolve(input);
  127. fileName = path.basename(input, '.js');
  128. srcItems = fs.readFileSync(process.env.srcLang).toString().split(/msgstr \"[^\"]*/);
  129. // extract hash of strings only
  130. json = JSON.parse(fs.readFileSync(input).toString().replace(/^[\s\S]*plupload.addI18n\((\{[^\}]+\})\);[\s\S]*$/, '$1'));
  131. srcItems.forEach(function(srcItem, idx) {
  132. var m = srcItem.match(/msgid \"([\s\S]*?[^\\]|)\"/);
  133. if (m) {
  134. srcItems[idx] += 'msgstr "';
  135. if (json[m[1]]) {
  136. srcItems[idx] += json[m[1]].replace(/([^\\])\"/g, '$1\\"');
  137. }
  138. }
  139. });
  140. fs.writeFileSync(path.join(to, fileName+'.po'), srcItems.join(''));
  141. console.info(fileName + ".po generated.");
  142. }
  143. if (!process.env.srcLang) {
  144. console.info("Source language not specified!");
  145. process.exit(1);
  146. }
  147. findFiles(from).forEach(generatePOT);
  148. };
  149. var pull = function(srcUrl, to, cb) {
  150. console.info("Fetching fresh internationalization files...")
  151. srcUrl = srcUrl.replace(/\/*$/, '');
  152. var langsUrl = srcUrl + '/?details';
  153. to = to || process.env.to || './tmp/i18n';
  154. if (fs.existsSync(to)) {
  155. jake.rmRf(to);
  156. }
  157. jake.mkdirP(to);
  158. function generateLangFile(lang, strs) {
  159. var langStr = lang.name + " (" + lang.code + ")";
  160. var data = {};
  161. strs.forEach(function(str) {
  162. data[str.source_string] = str.translation;
  163. });
  164. fs.writeFileSync(path.join(to, lang.code+".js"), "// " + langStr + "\n" + "plupload.addI18n(" + JSON.stringify(data) + ");");
  165. console.info(langStr + " fetched.");
  166. }
  167. function fetchLangs(stats) {
  168. stats = stats || {};
  169. request(langsUrl, function (error, response, body) {
  170. if (error || response.statusCode != 200) {
  171. console.log(srcUrl + " not reachable.");
  172. process.exit(1);
  173. }
  174. /*
  175. {
  176. "slug": "txc"
  177. "mimetype": "text/x-po",
  178. "source_language_code": "en",
  179. "wordcount": 6160,
  180. "total_entities": 1017,
  181. "last_update": "2011-12-05 19:59:55",
  182. "available_languages": [
  183. {
  184. "code_aliases": " ",
  185. "code": "sq",
  186. "name": "Albanian"
  187. },
  188. ...
  189. ],
  190. }
  191. */
  192. var data = JSON.parse(body);
  193. var queue = [];
  194. var langUrl = srcUrl + '/translation/%s/strings/';
  195. data.available_languages.forEach(function(lang) {
  196. // ignore language packs that weren't even started
  197. if (!stats[lang.code] || !stats[lang.code].translated_entities) {
  198. return;
  199. }
  200. queue.push(function(cb) {
  201. request(util.format(langUrl, lang.code), function(error, response, body) {
  202. if (error || response.statusCode != 200) {
  203. console.log(lang.name + " not fetched.");
  204. } else {
  205. generateLangFile(lang, JSON.parse(body));
  206. }
  207. cb();
  208. });
  209. });
  210. });
  211. async.parallel(queue, function() {
  212. if (typeof(cb) == 'function') {
  213. cb();
  214. }
  215. });
  216. });
  217. }
  218. request(srcUrl + '/stats', function (error, response, body) {
  219. if (error || response.statusCode != 200) {
  220. console.log(srcUrl + " not reachable.");
  221. process.exit(1);
  222. }
  223. /*
  224. {
  225. sl: {
  226. reviewed_percentage: '0%',
  227. completed: '0%',
  228. untranslated_words: 139,
  229. last_commiter: 'jayarjo',
  230. reviewed: 0,
  231. translated_entities: 0,
  232. translated_words: 0,
  233. last_update: '2015-07-23 16:45:09',
  234. untranslated_entities: 38
  235. }
  236. ...
  237. }
  238. */
  239. fetchLangs(JSON.parse(body));
  240. });
  241. };
  242. module.exports = {
  243. toPot: toPot,
  244. extract: extract,
  245. pull: pull
  246. };