@criticalmanufacturing/dev-i18n-transform
Version:
i18n <--> gettext transform
196 lines • 8.48 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
const package_1 = require("../model/package");
const file_1 = require("../model/file");
const message_1 = require("../model/message");
const index_1 = require("../logger/index");
const fs = require("fs");
const util_1 = require("../util");
const translation_1 = require("../model/translation");
/**
* PO (Portable Object) parser.
* Parses a set of PO files into a {@see Package}
*/
class PortableObjectParser {
/**
* PortableObject (PO) File(s) Analyser
* @param packagePath Absolute package path
* @param filePaths File paths to analyse
*/
constructor(packagePath, filePaths) {
/**
* Utility instance
*/
this._util = new util_1.Util();
this._filePaths = filePaths;
this._package = new package_1.Package(packagePath);
}
parseFile(filePath) {
// Read file into a string
let data = fs.readFileSync(filePath, "utf-8");
// Split the file content by empty new lines
// The first entry is the PO Header
let entries = this._util.splitByEmptyLine(data);
if (entries != null && entries.length > 0) {
let headerInfo = this.parseHeader(entries[0]);
if (headerInfo.packageName !== this._package.name) {
index_1.default.warn(`The package '${this._package.name}' doesn't match the original package name saved on the header of '${filePath}'`);
}
// Remove the first element of the array
entries.splice(0, 1);
for (let entry of entries) {
this.parseEntry(entry);
}
}
else {
// File contains no entries. Raise a warning
index_1.default.warn(`File '${filePath}' as no entries. Continuing...`);
}
}
parseEntry(entry) {
// Entry example:
// #: test\mocks\multilevelExample\mock.pt-PT.ts#objects.WIZARD
// #: test\mocks\duplicatedTextExample\mock.pt-PT.ts#TEXT_1
// msgid "Wizard"
// msgstr "Wizard"
let filesInfo = this.extractFileInformation(entry);
let translationInfo = this.extractTranslation(entry);
let notes = this.extractNotesForTranslators(entry);
filesInfo.forEach((fileInfo) => {
let message = new message_1.Message(fileInfo.objectPath);
let translation = new translation_1.Translation(fileInfo.details.language, translationInfo.value, notes.indexOf(translation_1.TranslatorNotes.AutomaticTranslation) >= 0);
message.addOrUpdateTranslation(translation);
let file = new file_1.File(fileInfo.filePath);
if (fileInfo.references && fileInfo.references.length > 0) {
for (let ref of fileInfo.references) {
file.addOrUpdateReference(ref);
}
}
file.addOrUpdateMessage(message);
// Add the file to the package
this._package.addOrUpdateFile(file);
});
}
extractFileInformation(info) {
let results = [];
// Extract file information from rows
// #: test\mocks\multilevelExample\mock.pt-PT.ts#objects.WIZARD
// #: test\mocks\multilevelExample\mock.pt-PT.ts#objects.WIZARD test\mocks\multilevelExample\mock.pt-PT.ts#objects.WIZARD2
const filePathRegex = /^#: (.+)$/gm;
let filePathMatch;
while ((filePathMatch = filePathRegex.exec(info)) !== null) {
let pathLine = filePathMatch[1];
// Because we may have multiple files in the same line...
let paths = pathLine.match(/\s?(\S*)#(\S*)/g);
for (let path of paths) {
let fileAndObjectMatch = /(.*)#(.*)/.exec(path.trim());
let filePath = fileAndObjectMatch[1];
let objectPath = fileAndObjectMatch[2];
if (objectPath === ".")
console.log(path);
// Extract file information from row
// Accept single or multiple lines for references
// # AddReference | import i18n from "./reference.default";
// # AddReference | import i18n from "./reference.default" | filePath;
let references = [];
let referencesMatch = info.match(/^# (.*?) \| (.+)$/gm);
if (referencesMatch != null && referencesMatch.length > 0) {
for (let i = 0; i < referencesMatch.length; i++) {
// Get type of comment first and its params
let ref = /^# (.*?) \| (.+)$/.exec(referencesMatch[i]);
// Split the params by | and trim
let params = ref[2].split("|").map(p => p.trim());
let typeOfComment = ref[1];
switch (typeOfComment) {
case "AddReference":
// If there is a file, ignore references that are not for this file
if (params[1] && params[1] !== filePath)
continue;
references.push(params[0]);
break;
default:
index_1.default.warn(`Unknown type of entry found: '${typeOfComment}'`);
break;
}
}
}
results.push({
filePath: filePath,
objectPath: objectPath,
details: file_1.File.parseFileName(filePath),
references: references
});
}
}
return results;
}
/**
*
* @param entry Translation entry to parse
*/
extractTranslation(entry) {
// Match full translation (may include multiple lines)
let fullTranslationMatch = /msgstr (.*)(\n"?.*"?)*/.exec(entry);
let fullTranslationMessage = fullTranslationMatch[0];
// Split translation by lines and parse each one
let translatedMessage = "";
for (let translationLine of fullTranslationMessage.split("\n")) {
let parsedLine = /(?:msgstr )?(.*)/.exec(translationLine)[1];
// Remove "" and trim the message
if (parsedLine.startsWith("\""))
parsedLine = parsedLine.slice(1);
if (parsedLine.endsWith("\""))
parsedLine = parsedLine.slice(0, -1);
// parsedLine = parsedLine.trim();
translatedMessage += parsedLine;
}
return {
value: translatedMessage
};
}
/**
* Extract translators notes from the translation entry.
* Translation notes currently supported:
* - 001 | Automated Translation
*
* @param entry Translation entry to extract the notes from
* @return An array of translator notes.
*/
extractNotesForTranslators(entry) {
let notes = [];
let match = /#\. (.+?)\s*\|\s*(.+)/gm.exec(entry);
// Translator notes are not mandatory, so they may not exist
if (match != null && match.length === 3) {
let noteId = parseInt(match[1]);
// Validate that this not exists
if (!(noteId in translation_1.TranslatorNotes)) {
index_1.default.warn(`Unknown translator note '${match[0]}'`);
}
else {
notes.push(noteId);
}
}
return notes;
}
/**
* Parses the header of the PO file.
*
* @param header Header of the file to parse
* @return Information about the package
*/
parseHeader(header) {
// Check if we have the package name on the file header
let regexMatch = /[^]\# OriginalPackageName\: (.+)/.exec(header);
return {
packageName: regexMatch[1]
};
}
run() {
for (let path of this._filePaths) {
this.parseFile(path);
}
return this._package;
}
}
exports.PortableObjectParser = PortableObjectParser;
//# sourceMappingURL=portableObject.parser.js.map