Files
NeoTachyon/tools/po2lmo.js
T

135 lines
3.6 KiB
JavaScript

#!/usr/bin/env node
'use strict';
const fs = require('fs');
// Paul Hsieh's SuperFastHash
function sfh_hash(data) {
let hash = 0;
let i = data.length;
let k;
while (i >= 4) {
k = (data.charCodeAt(i - 4) & 0xff) |
((data.charCodeAt(i - 3) & 0xff) << 8) |
((data.charCodeAt(i - 2) & 0xff) << 16) |
((data.charCodeAt(i - 1) & 0xff) << 24);
k = (k * 0x5bd1e995) >>> 0;
k ^= k >>> 24;
k = (k * 0x5bd1e995) >>> 0;
hash = (hash * 0x5bd1e995 + k) >>> 0;
i -= 4;
}
switch (i) {
case 3: hash ^= (data.charCodeAt(i - 3) & 0xff) << 16;
case 2: hash ^= (data.charCodeAt(i - 2) & 0xff) << 8;
case 1: hash ^= (data.charCodeAt(i - 1) & 0xff);
hash = (hash * 0x5bd1e995) >>> 0;
}
hash ^= hash >>> 13;
hash = (hash * 0x5bd1e995) >>> 0;
hash ^= hash >>> 15;
return hash >>> 0;
}
function pad4(len) {
return (4 - (len % 4)) % 4;
}
function parsePo(content) {
const entries = [];
const lines = content.split('\n');
let state = null;
let msgid = '';
let msgstr = '';
let key = '';
function flush() {
if (state === 'msgstr' && msgid !== '' && msgstr !== '') {
entries.push({ key: msgid, value: msgstr });
}
msgid = '';
msgstr = '';
state = null;
}
for (let i = 0; i < lines.length; i++) {
const line = lines[i];
if (line.startsWith('msgid ')) {
flush();
state = 'msgid';
msgid = line.slice(7, -1).replace(/\\n/g, '\n').replace(/\\"/g, '"');
} else if (line.startsWith('msgstr ')) {
state = 'msgstr';
msgstr = line.slice(8, -1).replace(/\\n/g, '\n').replace(/\\"/g, '"');
} else if (line.startsWith('"')) {
const val = line.slice(1, -1).replace(/\\n/g, '\n').replace(/\\"/g, '"');
if (state === 'msgid') msgid += val;
else if (state === 'msgstr') msgstr += val;
}
}
flush();
return entries;
}
function compileLmo(entries) {
const payload = [];
const index = [];
let offset = 0;
for (const entry of entries) {
const keyBuf = Buffer.from(entry.key, 'utf8');
const valBuf = Buffer.from(entry.value, 'utf8');
const keyId = sfh_hash(entry.key);
const valId = sfh_hash(entry.value);
// Write value to payload
const paddedLen = valBuf.length + pad4(valBuf.length);
const padded = Buffer.alloc(paddedLen, 0);
valBuf.copy(padded);
payload.push(padded);
index.push({
keyId,
valId,
offset,
length: valBuf.length,
});
offset += paddedLen;
}
// Build index section
const indexBuf = Buffer.alloc(index.length * 16);
for (let i = 0; i < index.length; i++) {
indexBuf.writeUInt32BE(index[i].keyId, i * 16);
indexBuf.writeUInt32BE(index[i].valId, i * 16 + 4);
indexBuf.writeUInt32BE(index[i].offset, i * 16 + 8);
indexBuf.writeUInt32BE(index[i].length, i * 16 + 12);
}
// Total: payload + index + 4 bytes for index offset
const payloadData = Buffer.concat(payload);
const totalLen = payloadData.length + indexBuf.length + 4;
const out = Buffer.alloc(totalLen);
payloadData.copy(out, 0);
indexBuf.copy(out, payloadData.length);
out.writeUInt32BE(payloadData.length, totalLen - 4);
return out;
}
// Main
const [input, output] = process.argv.slice(2);
if (!input || !output) {
console.error('Usage: node po2lmo.js input.po output.lmo');
process.exit(1);
}
const poContent = fs.readFileSync(input, 'utf8');
const entries = parsePo(poContent);
console.log(`Parsed ${entries.length} translation entries from ${input}`);
const lmo = compileLmo(entries);
fs.writeFileSync(output, lmo);
console.log(`Written ${lmo.length} bytes to ${output}`);