Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix missing types and jsDocs #89

Merged
merged 17 commits into from
May 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions .eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,7 @@
"es2021": true,
"node": true
},
"extends": ["eslint:recommended", "plugin:@typescript-eslint/recommended"],
"parser": "@typescript-eslint/parser",
"extends": "standard",
"parserOptions": {
"ecmaVersion": "latest",
"sourceType": "module"
Expand Down
17 changes: 13 additions & 4 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,24 @@ on:
- push
- pull_request
jobs:
build:
name: Build with tsc
runs-on: ubuntu-latest
continue-on-error: true
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: lts/*
- run: npm install
- run: npx tsc
test:
name: Node.js ${{ matrix.node-version }} on ${{ matrix.os }}
name: Test ${{ matrix.node-version }} on ${{ matrix.os }}
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
node-version:
- 18
- 20
node: [ 18, 20 ]
os:
- ubuntu-latest
- windows-latest
Expand Down
2 changes: 0 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,6 @@
"@types/content-type": "^1.1.8",
"@types/mocha": "latest",
"@types/readable-stream": "^4.0.11",
"@typescript-eslint/eslint-plugin": "^6.18.1",
"@typescript-eslint/parser": "^6.14.0",
"chai": "^5.0.3",
"eslint": "^8.56.0",
"eslint-config-standard": "^17.1.0",
Expand Down
52 changes: 0 additions & 52 deletions src/index.d.ts

This file was deleted.

12 changes: 4 additions & 8 deletions src/index.js
Original file line number Diff line number Diff line change
@@ -1,25 +1,21 @@
import * as poParser from './poparser.js';
import { poParse, poStream } from './poparser.js';
import poCompiler from './pocompiler.js';
import moParser from './moparser.js';
import moCompiler from './mocompiler.js';

/**
* Translation parser and compiler for PO files
* @see https://www.gnu.org/software/gettext/manual/html_node/PO.html
*
* @type {import("./index.d.ts").po} po
*/
export const po = {
parse: poParser.parse,
createParseStream: poParser.stream,
parse: poParse,
createParseStream: poStream,
compile: poCompiler
};

/**
* Translation parser and compiler for PO files
* Translation parser and compiler for MO files
* @see https://www.gnu.org/software/gettext/manual/html_node/MO.html
*
* @type {import("./index.d.ts").mo} mo
*/
export const mo = {
parse: moParser,
Expand Down
142 changes: 92 additions & 50 deletions src/mocompiler.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,30 @@ import encoding from 'encoding';
import { HEADERS, formatCharset, generateHeader, compareMsgid } from './shared.js';
import contentType from 'content-type';

/**
* @typedef {import('node:stream').Transform} Transform
* @typedef {import('./types.js').GetTextTranslation} GetTextTranslation
* @typedef {import('./types.js').GetTextTranslations} GetTextTranslations
* @typedef {import('./types.js').Translations} Translations
* @typedef {import('./types.js').WriteFunc} WriteFunc
*/

/**
* @typedef {Object} Size Data about the size of the compiled MO object.
* @property {number} msgid The size of the msgid section.
* @property {number} msgstr The size of the msgstr section.
* @property {number} total The total size of the compiled MO object.
*/

/**
* @typedef {{ msgid: Buffer, msgstr: Buffer }} TranslationBuffers A translation object partially parsed.
*/

/**
* Exposes general compiler function. Takes a translation
* object as a parameter and returns binary MO object
*
* @param {import('./index.d.ts').GetTextTranslations} table Translation object
* @param {GetTextTranslations} table Translation object
* @return {Buffer} Compiled binary MO object
*/
export default function (table) {
Expand All @@ -16,66 +35,85 @@ export default function (table) {
}

/**
* Creates a MO compiler object.
*
* @constructor
* @param {import('./index.d.ts').GetTextTranslations} table Translation table as defined in the README
* @return {import('./index.d.ts').Compiler} Compiler
* Prepare the header object to be compatible with MO compiler
* @param {Record<string, string>} headers the headers
* @return {Record<string, string>} The prepared header
*/
function Compiler (table = {}) {
this._table = table;

let { headers = {}, translations = {} } = this._table;

headers = Object.keys(headers).reduce((result, key) => {
function prepareMoHeaders (headers) {
return Object.keys(headers).reduce((result, key) => {
const lowerKey = key.toLowerCase();

if (HEADERS.has(lowerKey)) {
// POT-Creation-Date is removed in MO (see https://savannah.gnu.org/bugs/?49654)
if (lowerKey !== 'pot-creation-date') {
result[HEADERS.get(lowerKey)] = headers[key];
const value = HEADERS.get(lowerKey);
if (value) {
result[value] = headers[key];
}
}
} else {
result[key] = headers[key];
}

return result;
}, {});
}, /** @type {Record<string, string>} */ ({}));
}

// filter out empty translations
translations = Object.keys(translations).reduce((result, msgctxt) => {
/**
* Prepare the translation object to be compatible with MO compiler
* @param {Translations} translations
* @return {Translations}
*/
function prepareTranslations (translations) {
return Object.keys(translations).reduce((result, msgctxt) => {
const context = translations[msgctxt];
const msgs = Object.keys(context).reduce((result, msgid) => {
const hasTranslation = context[msgid].msgstr.some(item => !!item.length);
const TranslationMsgstr = context[msgid].msgstr;
const hasTranslation = TranslationMsgstr.some(item => !!item.length);

if (hasTranslation) {
result[msgid] = context[msgid];
}

return result;
}, {});
}, /** @type {Record<string, GetTextTranslation>} */({}));

if (Object.keys(msgs).length) {
result[msgctxt] = msgs;
}

return result;
}, {});
}, /** @type {Translations} */({}));
}

this._table.translations = translations;
this._table.headers = headers;
/**
* Creates a MO compiler object.
* @this {Compiler & Transform}
*
* @param {GetTextTranslations} [table] Translation table as defined in the README
*/
function Compiler (table) {
/** @type {GetTextTranslations} _table The translation table */
this._table = {
charset: undefined,
translations: prepareTranslations(table?.translations ?? {}),
headers: prepareMoHeaders(table?.headers ?? {})
};

this._translations = [];

/**
* @type {WriteFunc}
*/
this._writeFunc = 'writeUInt32LE';

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can this be removed?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That assignment appears to have been established at some point, presumably intended for modification through options. However, with no implementation efforts undertaken, it remains as unused memory.

https://nodejs.org/api/buffer.html#bufwriteuint32levalue-offset

These serve as the 'placeholders' for the associated function name utilized for reading bytes from the po/mo files. Frankly, I'm uncertain whether it's worthwhile to implement the others, as file writing should adhere to a similar approach.

this made typing more complicated, what do you say I leave it or is it OK?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would argue that byte order should be an option of the compiler, so rather than remove the concept of changing the write function we should fix it.

If a mo file is parsed in a specific byte order it should be rewritten in the same order.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should be an option of the compiler

i think this requires a separate issue. won't fix in this pr


this._handleCharset();
}

/**
* Magic bytes for the generated binary data
*/
Compiler.prototype.MAGIC = 0x950412de;
/**
* Magic bytes for the generated binary data
* @type {number} MAGIC file header magic value of mo file
*/
this.MAGIC = 0x950412de;
}

/**
* Handles header values, replaces or adds (if needed) a charset property
Expand All @@ -96,17 +134,19 @@ Compiler.prototype._handleCharset = function () {

/**
* Generates an array of translation strings
* in the form of [{msgid:... , msgstr:...}]
* in the form of [{msgid:..., msgstr: ...}]
*
* @return {Array} Translation strings array
*/
Compiler.prototype._generateList = function () {
/** @type {TranslationBuffers[]} */
const list = [];

list.push({
msgid: Buffer.alloc(0),
msgstr: encoding.convert(generateHeader(this._table.headers), this._table.charset)
});
if ('headers' in this._table) {
list.push({
msgid: Buffer.alloc(0),
msgstr: encoding.convert(generateHeader(this._table.headers), this._table.charset)
});
}

Object.keys(this._table.translations).forEach(msgctxt => {
if (typeof this._table.translations[msgctxt] !== 'object') {
Expand All @@ -133,7 +173,7 @@ Compiler.prototype._generateList = function () {
key += '\u0000' + msgidPlural;
}

const value = [].concat(this._table.translations[msgctxt][msgid].msgstr || []).join('\u0000');
const value = /** @type {string[]} */([]).concat(this._table.translations[msgctxt][msgid].msgstr ?? []).join('\u0000');

list.push({
msgid: encoding.convert(key, this._table.charset),
Expand All @@ -148,20 +188,19 @@ Compiler.prototype._generateList = function () {
/**
* Calculate buffer size for the final binary object
*
* @param {import('./index.d.ts').GetTextTranslations} list An array of translation strings from _generateList
* @return {Object} Size data of {msgid, msgstr, total}
* @param {TranslationBuffers[]} list An array of translation strings from _generateList
* @return {Size} Size data of {msgid, msgstr, total}
*/
Compiler.prototype._calculateSize = function (list) {
let msgidLength = 0;
let msgstrLength = 0;
let totalLength = 0;

list.forEach(translation => {
msgidLength += translation.msgid.length + 1; // + extra 0x00
msgstrLength += translation.msgstr.length + 1; // + extra 0x00
});

totalLength = 4 + // magic number
const totalLength = 4 + // magic number
4 + // revision
4 + // string count
4 + // original string table offset
Expand All @@ -183,9 +222,9 @@ Compiler.prototype._calculateSize = function (list) {
/**
* Generates the binary MO object from the translation list
*
* @param {import('./index.d.ts').GetTextTranslations} list translation list
* @param {Object} size Byte size information
* @return {Buffer} Compiled MO object
* @param {TranslationBuffers[]} list translation list
* @param {Size} size Byte size information
* @return {Buffer} Compiled MO object
*/
Compiler.prototype._build = function (list, size) {
const returnBuffer = Buffer.alloc(size.total);
erikyo marked this conversation as resolved.
Show resolved Hide resolved
Expand Down Expand Up @@ -214,21 +253,23 @@ Compiler.prototype._build = function (list, size) {
// hash table offset
returnBuffer[this._writeFunc](28 + (4 + 4) * list.length * 2, 24);

// build originals table
// Build original table
curPosition = 28 + 2 * (4 + 4) * list.length;
for (i = 0, len = list.length; i < len; i++) {
list[i].msgid.copy(returnBuffer, curPosition);
returnBuffer[this._writeFunc](list[i].msgid.length, 28 + i * 8);
returnBuffer[this._writeFunc](curPosition, 28 + i * 8 + 4);
const msgidLength = /** @type {Buffer} */(/** @type {unknown} */(list[i].msgid));
msgidLength.copy(returnBuffer, curPosition);
returnBuffer.writeUInt32LE(list[i].msgid.length, 28 + i * 8);
returnBuffer.writeUInt32LE(curPosition, 28 + i * 8 + 4);
returnBuffer[curPosition + list[i].msgid.length] = 0x00;
curPosition += list[i].msgid.length + 1;
}

// build translations table
// build translation table
for (i = 0, len = list.length; i < len; i++) {
list[i].msgstr.copy(returnBuffer, curPosition);
returnBuffer[this._writeFunc](list[i].msgstr.length, 28 + (4 + 4) * list.length + i * 8);
returnBuffer[this._writeFunc](curPosition, 28 + (4 + 4) * list.length + i * 8 + 4);
const msgstrLength = /** @type {Buffer} */(/** @type {unknown} */(list[i].msgstr));
msgstrLength.copy(returnBuffer, curPosition);
returnBuffer.writeUInt32LE(list[i].msgstr.length, 28 + (4 + 4) * list.length + i * 8);
returnBuffer.writeUInt32LE(curPosition, 28 + (4 + 4) * list.length + i * 8 + 4);
returnBuffer[curPosition + list[i].msgstr.length] = 0x00;
curPosition += list[i].msgstr.length + 1;
}
Expand All @@ -237,8 +278,9 @@ Compiler.prototype._build = function (list, size) {
};

/**
* Compiles translation object into a binary MO object
* Compiles a translation object into a binary MO object
*
* @interface
* @return {Buffer} Compiled MO object
*/
Compiler.prototype.compile = function () {
Expand Down
Loading