PO2JSON converter for Vivaldi translation purposes

Outdated. Use André’s online converter instead.

You can find it here: https://an-dz.github.io/PurePO2JSON/

Translating Vivaldi was always a bit difficult when it came to string lengths or if we needed to see exactly where the string is and if it is the right one in that place. Luckily An_dz invested some time to write a nice little JavaScript for the dev console that can convert files from the POEDIT format used for translation to the JSON format used in Vivaldi, which works well …

… but I am a lazy guy and did not want to copy the file to the console each time I needed to test new translations, so I modified some (few) lines in his script, added some file read/write stuff and stuffed it into a simple webpage. When run locally, it allows to load the .po file,  convert and save it as messages.json file. This file can be used with Vivaldi to test the translations.


Alternatively you can use ludev‘s online converter which is based on this script. It can either download the PO file directly from Transifex, convert it for you and store it to your computer for further use – or you can use your local PO file for the conversion.

The online converter is availlable at http://ludev.rocks/vivaldipo2json/.


The code:

<!DOCTYPE html> <html>      <head>         <meta http-equiv="Content-Type" content="text/html; charset=utf-8">         <title>Convert Vivaldi PO to JSON</title>         <style>             body {                 font-family: sans-serif;             }             textarea {                 width: 90%;                 height: 600px;             }          </style>     </head>      <body>         <form>             <div>                 <p id="status">File read</p>                 <textarea id="output"></textarea>             </div>             <input type="file" id="files" name="files">         </form>         <script>             function convertToJson(file) {                 'use strict';                 // VivaldiPO2JSON v1.1                 // by André Zanghelini (An_dz)                 /* modified by Roland Reck (QuHno) to work with the web page,  				* added some missing "," and "=" 				* added some {}, replaced i++ by i += 1 and moved some var declarations to other places because JSLint was complaining 				* 2016-01-25: changed order of functions so that they are already defined _before_ they are caled. 				* 2016-01-25: replace multiple space by one 				*/                  var textArea = document.getElementById('output'),                     start = 0,                     end = 0,                     i = 0,                     oldStr = '',                     newStr = '',                     s,                     tempStr = '', 					arr = [], 					msgid;                  // Remove useless header stuff                 file = file.replace(/msgid ""\n/, "");                 file = "{" + file.substring(file.indexOf("msgid"));                 // Remove other comments                 file = file.replace(/(#.*\n|"\n")/g, "");                 // Change translation strings to JSON message                 file = file.replace(/msgstr (".*")\n/g, "    {\"message\":$1},");                 // Change msgid so Vivaldi can understand them                 // Special and uppercase chars must be decimal unicode value                 tempStr = file;                 // While we still find "msgid "                 while (start > -1) {                     start = tempStr.search(/msgid /);                     if (start > -1) {                         end = start + tempStr.substr(start).search(/\n/);                         oldStr = tempStr.substring(start, end);                         newStr = oldStr.replace(/msgid /, "");                         s = 0; 						// Q-mod: Replace multiple whitespace by one 						newStr = newStr.replace(/\s+/g, " ");                         // We change the special characters for later manipulation                         newStr = newStr.replace(/([A-Z(){}å\[\]—.,;:\/\\<>\=\+*#@'\|!’%&$“”?\_\-]| )/g, "%$1_");                         // Now we get rid of the newlines which are written as \n                         // Remember that the above added a % in front of \n and an _ after \                         newStr = newStr.replace(/%\\_n/g, "_10_");                         // Add 0 at end of string name and : at end of string                         newStr = newStr.replace(/(.)"/, "$10\":");                         // Replace special chars and uppercase to char code                         while (s !== -1) {                             s = newStr.search(/%/);                             if (s > -1) {                                 newStr = newStr.replace(/%./, "_" + newStr.charCodeAt(s + 1)); 							}                         }                          file = file.replace(oldStr, newStr);                         // Just search where we did not search before                         tempStr = tempStr.substr(end + 1);                     }                 }                  // Replace plurals, start copying msgid                 arr = file.split("\nmsgid_plural");                 file = arr[0];                 for (i = 1; i < arr.length; i += 1) {                     // Take msgid from previous part                     msgid = arr[i - 1].substring(arr[i - 1].lastIndexOf("\n\"") + 1, arr[i - 1].lastIndexOf(":") - 2);                     // remove msgid_plural and replace msgstr[0] in current part                     arr[i] = arr[i].replace(/.*\nmsgstr\[0\] (".*")/, "\n    {\"message\":$1},");                     // Replace other variations                     arr[i] = arr[i].replace(/msgstr\[(\d+)\] (".*")/g, msgid + "$1\":\n    {\"message\":$2},");                     // Join again to main                     file = file.concat(arr[i]);                 }                  // Add msgctxt string                 arr = file.split("msgctxt ");                 file = arr[0];                 for (i = 1; i < arr.length; i += 1) {                     // Add # at begining, this will help our selector                     arr[i] = "#" + arr[i];                     // Find first newline                     end = arr[i].search(/\n/);                     // Find spaces, special chars and uppercase in first line                     newStr = arr[i].substring(1, end - 1).replace(/([A-Z(){}\[\].,;:\/\\<>\=\+*#@'\|!%&$“”?\_\-]| )/g, "%$1_");                     // Replace special chars and uppercase to char code                     s = 0;                     while (s !== -1) {                         s = newStr.search(/%/);                         if (s > -1) {                             newStr = newStr.replace(/%./, "_" + newStr.charCodeAt(s + 1)); 						}                     }                     arr[i] = arr[i].replace(/#.*\n"/, newStr + "_4_");                     // arr[i] = arr[i].replace(/"\n"/, "_4_")                     file = file.concat(arr[i]);                 }                  // Remove double newline                 file = file.replace(/\n\n/g, "\n");                  // Replace last comma to closing bracket                 file = file.substring(0, file.lastIndexOf(",")) + "\n}";                  // Replace on screen and alert                 textArea.textContent = file;                 document.getElementById('status').innerText = "File successfully converted";              }               function saveTextAsFile(fileNameToSaveAs) { 			    'use strict';                 var textToWrite = document.getElementById("output").value,                     textFileAsBlob = new Blob([textToWrite], {                         type : 'application/json'                     }),                     downloadLink = document.createElement("a"); // Create temporary link. No need to add it to the DOM                  downloadLink.download = fileNameToSaveAs;                 downloadLink.innerText = "Download File";                 downloadLink.href = window.URL.createObjectURL(textFileAsBlob);                 downloadLink.click();                 // Do I need to remove the objectURL? 				window.URL.revokeObjectURL(downloadLink.href);             }  			function handleFileSelect(evt) { 			    'use strict';                 var files = document.getElementById('files').files,                     reader = new FileReader();                  if (!files.length) {                     alert('Please select a file!');                     return;                 }                  //var file = files[0];                 // Not the best check but should be sufficient                 if (files[0].name.search(/\.po$/i) === -1) {                     alert('Please select a .po file!');                     files = null;                     return;                 }                   reader.onloadend = function (evt) {                     if (evt.target.readyState === FileReader.DONE) { // DONE == 2                         convertToJson(evt.target.result);                         // Extract file name from e.g. "for_translation_vivaldi-localisation_vivaldipot_de.po" to "de.json" for saving                         var start = files[0].name.search(/vivaldipot/i),                             end = files[0].name.search(/\.po$/i),                             filename = files[0].name.substring(start + 11, end) + '.json';                         saveTextAsFile(filename);                     }                 };                 reader.readAsText(files[0], "UTF-8");             }               document.getElementById('files').addEventListener('change', handleFileSelect, false);         </script>     </body>  </html>



  • Save the code as VivaldiPO2JSON.html to your computer somewhere where you can find it again
  • Open it in Vivaldi
  • Choose the right PO file
  • Convert it
  • Save the converted file

All mistakes are mine An_dz code works just fine. Feel free to post improvements below 🙂


edit: Thanks to An_dz for the changes! (See his comment below) 🙂

Thanks to ludev for providing the online converter.

14 thoughts on “PO2JSON converter for Vivaldi translation purposes”

  1. You can be lazier by loading, converting and saving as JSON in one go:

    Remove/comment lines 26-30, 66-69, 87, 174, 175
    Change line 51 to:
    Change line 81 to:
    [quote]function convertToJson(file) {[/quote]
    Change line 60 to:
    [quote]function saveTextAsFile(fileNameToSaveAs) {[/quote]
    Add this after line 51:
    [quote]var start = files[0].name.search(/vivaldipot/i),
    end = files[0].name.search(/.po$/i),
    filename = files[0].name.substring(start+11, end) + ‘.json’;

    Strange, using bbcode code doesn’t work, My code doesn’t show up.

  2. I was thinking about doing it all in one step, but then I thought it would be nice to look at the conversion result before saving, so i did it that way – but in the end that idea was BS. I’ll include your changes.

    Thanks again 🙂

  3. At the end it still prints at the area so you can use as a fallback for browsers that do not create a file, like old Opera, and also to check the results.

    Maybe we should send this new version to the rest of the translator team.

  4. yepp.
    Done. look for the mail with the title:
    “Re: [Translators] JS for converting PO to JSON (new file!)”

    Now even JSlinted 🙂

    btw: Do you know this:

    Nice for comparing JSON files even if they are formatted differently. It helps a lot to see where a translation might break (I just found a string that is translated in the PO but still taken from Chromium because they changed one letter from lower to upper case)
    I wonder if it would make sense to integrate that too …

  5. Hi! I am impressed at this work and currently working on UI improvements!
    And also implemented a new feature that enables to load po file directly from Transifex(this only works on server because of CORS issue, thus I used php to avoid it). I will comment and mail a link on weekend.

  6. One such example: Right-click on a link “Open link in new incognito window”. This string and its translation comes directly from the default chromium localization, not from Vivaldi’s localization.

    There are other examples too, but mind:
    Those are no errors in or of po2json 🙂

  7. That could be interesting too.
    It should be possible to circumvent the CORS problem by putting it into a right-click context extension as I did with the image info stuff: I load the images in binary format and extract the information from that. That should work with PO files too…

  8. I have sent you a message, containing some code that leads to a conversion error. I hope my assumption that removing the g is enough
    my .replace() and regex-Fu is bad. Please take a look on it.

  9. I had spotted an error regarding empty untranslated messages that messed up the JSON file so that it didn’t work anymore and An_dz fixed it. A big Thank You for that 🙂

    I have updated the code in the the blog post.

  10. Hello, I think your blog might be having browser compatibility issues.
    When I look at your website in Safari, it looks fine
    but when opening in Internet Explorer, it has
    some overlapping. I just wanted to give you a quick heads up!

    Other then that, great blog!

Leave a Reply

Your email address will not be published. Required fields are marked *