gettext-parser
Advanced tools
Comparing version 6.0.0 to 7.0.0
@@ -6,11 +6,14 @@ const encoding = require('encoding'); | ||
module.exports.parse = parse; | ||
module.exports.stream = stream; | ||
/** | ||
* Parses a PO object into translation table | ||
* | ||
* @param {Buffer|String} buffer PO object | ||
* @param {String} [defaultCharset] Default charset to use | ||
* @return {Object} Translation object | ||
* @typedef {{ defaultCharset?: string, validation?: boolean }} Options | ||
* @param {string | Buffer} input PO object | ||
* @param {Options} [options] Optional options with defaultCharset and validation | ||
*/ | ||
module.exports.parse = function (buffer, defaultCharset) { | ||
const parser = new Parser(buffer, defaultCharset); | ||
function parse (input, options = {}) { | ||
const parser = new Parser(input, options); | ||
@@ -23,8 +26,8 @@ return parser.parse(); | ||
* | ||
* @param {String} [defaultCharset] Default charset to use | ||
* @param {String} [options] Stream options | ||
* @return {Stream} Transform stream | ||
* @typedef {{ defaultCharset: strubg, validation: boolean }} Options | ||
* @param {Options} [options] Optional options with defaultCharset and validation | ||
* @param {import('readable-stream').TransformOptions} [transformOptions] Optional stream options | ||
*/ | ||
module.exports.stream = function (defaultCharset, options) { | ||
return new PoParserTransform(defaultCharset, options); | ||
function stream (options = {}, transformOptions = {}) { | ||
return new PoParserTransform(options, transformOptions); | ||
}; | ||
@@ -36,7 +39,9 @@ | ||
* | ||
* @typedef {{ defaultCharset?: string, validation?: boolean }} Options | ||
* @constructor | ||
* @param {Buffer|String} fileContents PO object | ||
* @param {String} [defaultCharset] Default charset to use | ||
* @param {string | Buffer} fileContents PO object | ||
* @param {Options} options Options with defaultCharset and validation | ||
*/ | ||
function Parser (fileContents, defaultCharset = 'iso-8859-1') { | ||
function Parser (fileContents, { defaultCharset = 'iso-8859-1', validation = false }) { | ||
this._validation = validation; | ||
this._charset = defaultCharset; | ||
@@ -384,2 +389,6 @@ | ||
if (lastNode) { | ||
if (this._validation && 'msgid_plural' in lastNode) { | ||
throw new SyntaxError(`Multiple msgid_plural error: entry "${lastNode.msgid}" in "${lastNode.msgctxt || ''}" context has multiple msgid_plural declarations.`); | ||
} | ||
lastNode.msgid_plural = tokens[i].value; | ||
@@ -412,2 +421,37 @@ } | ||
/** | ||
* Validate token | ||
* | ||
* @param {Object} token Parsed token | ||
* @param {Object} translations Translation table | ||
* @param {string} msgctxt Message entry context | ||
* @param {number} nplurals Number of epected plural forms | ||
* @throws Will throw an error if token validation fails | ||
*/ | ||
Parser.prototype._validateToken = function ( | ||
{ | ||
msgid = '', | ||
msgid_plural = '', // eslint-disable-line camelcase | ||
msgstr = [] | ||
}, | ||
translations, | ||
msgctxt, | ||
nplurals | ||
) { | ||
if (!this._validation) { | ||
return; | ||
} | ||
if (msgid in translations[msgctxt]) { | ||
throw new SyntaxError(`Duplicate msgid error: entry "${msgid}" in "${msgctxt}" context has already been declared.`); | ||
// eslint-disable-next-line camelcase | ||
} else if (msgid_plural && msgstr.length !== nplurals) { | ||
// eslint-disable-next-line camelcase | ||
throw new RangeError(`Plural forms range error: Expected to find ${nplurals} forms but got ${msgstr.length} for entry "${msgid_plural}" in "${msgctxt}" context.`); | ||
// eslint-disable-next-line camelcase | ||
} else if (!msgid_plural && msgstr.length !== 1) { | ||
throw new RangeError(`Translation string range error: Extected 1 msgstr definitions associated with "${msgid}" in "${msgctxt}" context, found ${msgstr.length}.`); | ||
} | ||
}; | ||
/** | ||
* Compose a translation table from tokens object | ||
@@ -424,2 +468,3 @@ * | ||
}; | ||
let nplurals = 1; | ||
let msgctxt; | ||
@@ -452,4 +497,7 @@ | ||
table.headers = sharedFuncs.parseHeader(tokens[i].msgstr[0]); | ||
nplurals = sharedFuncs.parseNPluralFromHeadersSafely(table.headers, nplurals); | ||
} | ||
this._validateToken(tokens[i], table.translations, msgctxt, nplurals); | ||
table.translations[msgctxt][tokens[i].msgid] = tokens[i]; | ||
@@ -481,13 +529,9 @@ } | ||
* | ||
* @typedef {{ defaultCharset: strubg, validation: boolean }} Options | ||
* @constructor | ||
* @param {String} [defaultCharset] Default charset to use | ||
* @param {String} [options] Stream options | ||
* @param {Options} options Optional options with defaultCharset and validation | ||
* @param {import('readable-stream').TransformOptions} transformOptions Optional stream options | ||
*/ | ||
function PoParserTransform (defaultCharset, options) { | ||
if (!options && defaultCharset && typeof defaultCharset === 'object') { | ||
options = defaultCharset; | ||
defaultCharset = undefined; | ||
} | ||
this.defaultCharset = defaultCharset; | ||
function PoParserTransform (options, transformOptions) { | ||
this.options = options; | ||
this._parser = false; | ||
@@ -499,5 +543,5 @@ this._tokens = {}; | ||
this.initialTreshold = options.initialTreshold || 2 * 1024; | ||
this.initialTreshold = transformOptions.initialTreshold || 2 * 1024; | ||
Transform.call(this, options); | ||
Transform.call(this, transformOptions); | ||
this._writableState.objectMode = false; | ||
@@ -532,3 +576,3 @@ this._readableState.objectMode = true; | ||
this._parser = new Parser(chunk, this.defaultCharset); | ||
this._parser = new Parser(chunk, this.options); | ||
} else if (this._cacheSize) { | ||
@@ -586,3 +630,3 @@ // this only happens if we had an uncompleted 8bit sequence from the last iteration | ||
if (!this._parser && chunk) { | ||
this._parser = new Parser(chunk, this.defaultCharset); | ||
this._parser = new Parser(chunk, this.options); | ||
} | ||
@@ -589,0 +633,0 @@ |
module.exports.parseHeader = parseHeader; | ||
module.exports.parseNPluralFromHeadersSafely = parseNPluralFromHeadersSafely; | ||
module.exports.generateHeader = generateHeader; | ||
@@ -8,2 +9,3 @@ module.exports.formatCharset = formatCharset; | ||
// see https://www.gnu.org/software/gettext/manual/html_node/Header-Entry.html | ||
const PLURAL_FORMS = 'Plural-Forms'; | ||
const HEADERS = new Map([ | ||
@@ -19,3 +21,3 @@ ['project-id-version', 'Project-Id-Version'], | ||
['content-transfer-encoding', 'Content-Transfer-Encoding'], | ||
['plural-forms', 'Plural-Forms'] | ||
['plural-forms', PLURAL_FORMS] | ||
]); | ||
@@ -25,2 +27,4 @@ | ||
const PLURAL_FORM_HEADER_NPLURALS_REGEX = /nplurals\s*=\s*(?<nplurals>\d+)/; | ||
/** | ||
@@ -51,2 +55,22 @@ * Parses a header string into an object of key-value pairs | ||
/** | ||
* Attempts to safely parse 'nplurals" value from "Plural-Forms" header | ||
* | ||
* @param {Object} [headers = {}] An object with parsed headers | ||
* @returns {number} Parsed result | ||
*/ | ||
function parseNPluralFromHeadersSafely (headers = {}, fallback = 1) { | ||
const pluralForms = headers[PLURAL_FORMS]; | ||
if (!pluralForms) { | ||
return fallback; | ||
} | ||
const { | ||
groups: { nplurals } = { nplurals: '' + fallback } | ||
} = pluralForms.match(PLURAL_FORM_HEADER_NPLURALS_REGEX) || {}; | ||
return parseInt(nplurals, 10) || fallback; | ||
} | ||
/** | ||
* Joins a header object of key value pairs into a header string | ||
@@ -135,2 +159,3 @@ * | ||
* Comparator function for comparing msgid | ||
* | ||
* @param {Object} object with msgid prev | ||
@@ -137,0 +162,0 @@ * @param {Object} object with msgid next |
{ | ||
"name": "gettext-parser", | ||
"description": "Parse and compile gettext po and mo files to/from json, nothing more, nothing less", | ||
"version": "6.0.0", | ||
"version": "7.0.0", | ||
"author": "Andris Reinman", | ||
@@ -26,15 +26,15 @@ "contributors": [ | ||
"dependencies": { | ||
"content-type": "^1.0.4", | ||
"content-type": "^1.0.5", | ||
"encoding": "^0.1.13", | ||
"readable-stream": "^4.1.0", | ||
"readable-stream": "^4.3.0", | ||
"safe-buffer": "^5.2.1" | ||
}, | ||
"devDependencies": { | ||
"chai": "^4.3.6", | ||
"eslint": "^8.21.0", | ||
"chai": "^4.3.7", | ||
"eslint": "^8.39.0", | ||
"eslint-config-standard": "^17.0.0", | ||
"eslint-plugin-import": "^2.26.0", | ||
"eslint-plugin-import": "^2.27.5", | ||
"eslint-plugin-node": "^11.1.0", | ||
"eslint-plugin-promise": "^6.0.0", | ||
"mocha": "^10.0.0" | ||
"eslint-plugin-promise": "^6.1.1", | ||
"mocha": "^10.2.0" | ||
}, | ||
@@ -41,0 +41,0 @@ "keywords": [ |
@@ -14,3 +14,2 @@ gettext-parser [![ci](https://github.com/smhg/gettext-parser/actions/workflows/ci.yml/badge.svg)](https://github.com/smhg/gettext-parser/actions/workflows/ci.yml) | ||
### Parse PO files | ||
@@ -20,3 +19,3 @@ | ||
gettextParser.po.parse(input[, defaultCharset]) → Object | ||
gettextParser.po.parse(input[, options]) → Object | ||
@@ -26,4 +25,11 @@ Where | ||
* **input** is a *po* file as a Buffer or an unicode string. Charset is converted to unicode from other encodings only if the input is a Buffer, otherwise the charset information is discarded | ||
* **defaultCharset** is the charset to use if charset is not defined or is the default `"CHARSET"` (applies only if *input* is a Buffer) | ||
* **options** is an optional object with the following optional properties: | ||
* **defaultCharset** is the charset to use if charset is not defined or is the default `"CHARSET"` (applies only if *input* is a Buffer) | ||
* **validation** is a flag to turn on PO source file validation. The validation makes sure that: | ||
* there is exactly zero or one `msgid_plural` definition per translation entry; a `Multiple msgid_plural error` error gets thrown otherwise. | ||
* there are no duplicate entries with exact `msgid` values; a `Duplicate msgid error` error gets thrown otherwise. | ||
* the number of plural forms matches exactly the number from `nplurals` defined in `Plural-Forms` header for entries that have plural forms; a `Plural forms range error` error gets thrown otherwise. | ||
* the number of `msgstr` matches exacty the one (if `msgid_plural` is not defined) or the number from `nplurals` (if `msgid_plural` is defined); a `Translation string range error` error gets thrown otherwise. | ||
Method returns gettext-parser specific translation object (see below) | ||
@@ -43,8 +49,8 @@ | ||
gettextParser.po.createParseStream([defaultCharset][, streamOptions]) → Transform Stream | ||
gettextParser.po.createParseStream([options][, transformOptions]) → Transform Stream | ||
Where | ||
* **defaultCharset** is the charset to use if charset is not defined or is the default `"CHARSET"` | ||
* **streamOptions** are the standard stream options | ||
* **options** is an optional object, same as in `parse`. See [Parse PO files](#parse-po-files) section for details. | ||
* **transformOptions** are the standard stream options. | ||
@@ -87,2 +93,4 @@ **Example** | ||
### | ||
### Parse MO files | ||
@@ -89,0 +97,0 @@ |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
56775
12
1314
215
Updatedcontent-type@^1.0.5
Updatedreadable-stream@^4.3.0