parse.js 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144
  1. /**
  2. * Parse PO buffer to JSON
  3. *
  4. * @param {Buffer|String} buffer - Buffer PO object or unicode string with PO data
  5. * @param {Object} [options]
  6. * @return {Object|String} Translation JSON
  7. */
  8. const g2m = require('gettext-to-messageformat');
  9. module.exports = function (buffer, options) {
  10. // Setup options and load in defaults
  11. options = options || {};
  12. const defaults = {
  13. pretty: false,
  14. fuzzy: false,
  15. stringify: false,
  16. format: 'raw',
  17. domain: 'messages',
  18. charset: 'utf8',
  19. fullMF: false,
  20. mfOptions: {},
  21. };
  22. for (const property in defaults) {
  23. options[property] = 'undefined' !== typeof options[property] ? options[property] : defaults[property];
  24. }
  25. const mf = {};
  26. let mfTranslations = {};
  27. let result = {};
  28. // defer to gettext-to-messageformat for the 'mf' format option
  29. // use all g2m default replacements except for: pattern: /[\\{}#]/g, replacement: '\\$&'
  30. if (options.format === 'mf') {
  31. const poString = buffer.toString();
  32. // if the Plural-Forms header is missing, g2m needs a function or will throw an error
  33. const mfOptions = (poString.includes('"Plural-Forms:')) ? options.mfOptions : Object.assign({}, {
  34. pluralFunction: () => 0
  35. }, options.mfOptions);
  36. result = Object.keys(mfOptions).length > 0 ? g2m.parsePo(buffer, mfOptions) : g2m.parsePo(buffer);
  37. if (options.fullMF) {
  38. return options.stringify ? JSON.stringify(result, null, options.pretty ? ' ' : null) : result;
  39. }
  40. // simplify the output to only return the translations
  41. if (result) {
  42. if (result['translations'] && result['translations']['']) {
  43. mfTranslations = result['translations'][''];
  44. // include the default translations at the top level to keep compatibility as much as possible
  45. Object.keys(result['translations']).forEach(function (context) {
  46. if (context === '') {
  47. Object.keys(result['translations']['']).forEach(function (key) {
  48. mfTranslations[key] = result['translations'][''][key];
  49. });
  50. } else {
  51. mfTranslations[context] = result['translations'][context];
  52. }
  53. })
  54. } else {
  55. mfTranslations = result['translations'] || {};
  56. }
  57. }
  58. return options.stringify ? JSON.stringify(mfTranslations, null, options.pretty ? ' ' : null) : mfTranslations;
  59. }
  60. // Parse the PO file
  61. const parsed = require('gettext-parser').po.parse(buffer, defaults.charset);
  62. // Create gettext/Jed compatible JSON from parsed data
  63. const contexts = parsed.translations;
  64. Object.keys(contexts).forEach(function (context) {
  65. const translations = parsed.translations[context];
  66. const pluralForms = parsed.headers ? parsed.headers['plural-forms'] : '';
  67. Object.keys(translations).forEach(function (key, i) {
  68. const t = translations[key],
  69. translationKey = context.length ? context + '\u0004' + key : key,
  70. fuzzy = t.comments && t.comments.flag && t.comments.flag.match(/fuzzy/) !== null;
  71. if (!fuzzy || options.fuzzy) {
  72. if (options.format === 'jed') {
  73. result[translationKey] = [t.msgid_plural ? t.msgid_plural : null].concat(t.msgstr);
  74. } else {
  75. if (pluralForms === 'nplurals=1; plural=0;') {
  76. msgstr = t.msgid_plural ? [t.msgstr] : t.msgstr;
  77. result[translationKey] = [t.msgid_plural ? t.msgid_plural : null].concat(msgstr);
  78. } else {
  79. result[translationKey] = [t.msgid_plural ? t.msgid_plural : null].concat(t.msgstr);
  80. }
  81. }
  82. }
  83. // In the case of fuzzy or empty messages, use msgid(/msgid_plural)
  84. if (options['fallback-to-msgid'] && (fuzzy && !options.fuzzy || t.msgstr[0] === '')) {
  85. result[translationKey] = [t.msgid_plural ? t.msgid_plural : null]
  86. .concat(t.msgid_plural ? [key, t.msgid_plural] : [key]);
  87. }
  88. });
  89. });
  90. // Attach headers (overwrites any empty translation keys that may have somehow gotten in)
  91. if (parsed.headers) {
  92. result[''] = parsed.headers;
  93. }
  94. if (options.format === 'mf') {
  95. delete result[''];
  96. }
  97. // Make JSON fully Jed-compatible
  98. if (options.format.indexOf('jed') === 0) {
  99. const jed = {
  100. domain: options.domain,
  101. locale_data: {}
  102. };
  103. if (options.format === 'jed') {
  104. for (const key in result) {
  105. if (result.hasOwnProperty(key) && key !== '') {
  106. for (let i = 2; i < result[key].length; i++) {
  107. if ('' === result[key][i]) {
  108. result[key][i] = result[key][0];
  109. }
  110. }
  111. result[key].shift();
  112. }
  113. }
  114. }
  115. jed.locale_data[options.domain] = result;
  116. jed.locale_data[options.domain][''] = {
  117. domain: options.domain,
  118. plural_forms: result['']['plural-forms'],
  119. lang: result['']['language']
  120. };
  121. result = jed;
  122. }
  123. return options.stringify ? JSON.stringify(result, null, options.pretty ? ' ' : null) : result;
  124. };