Files
echo-nexus/node_modules/po-parser/dist/index.js
T

269 lines
11 KiB
JavaScript

class POParser {
static parse(content) {
const lines = POParser.splitLines(content);
const messages = [];
const meta = {};
let state = 'entry';
let entry;
for(let i = 0; i < lines.length; i++){
const line = lines[i].trim();
// An empty line indicates the end of an entry
if (!line) {
if (state === 'entry' && entry) {
messages.push(POParser.finishEntry(entry));
entry = undefined;
}
state = 'entry';
continue;
}
if (state === 'meta') {
if (line.startsWith(POParser.QUOTE)) {
const rawMetaLine = POParser.extractQuotedString(line, state);
const metaLine = POParser.unescape(rawMetaLine);
const cleaned = metaLine.endsWith('\n') ? metaLine.slice(0, -1) : metaLine;
const separatorIndex = cleaned.indexOf(POParser.META_SEPARATOR);
if (separatorIndex > 0) {
const key = cleaned.substring(0, separatorIndex).trim();
const value = cleaned.substring(separatorIndex + 1).trim();
meta[key] = value;
}
} else {
POParser.throwWithLine('Encountered unexpected non-quoted metadata line', line);
}
} else {
// Unsupported comment types
if (POParser.lineStartsWithPrefix(line, POParser.COMMENTS.TRANSLATOR)) {
POParser.throwWithLine('Translator comments (#) are not supported, use inline descriptions instead', line);
}
if (POParser.lineStartsWithPrefix(line, POParser.COMMENTS.PREVIOUS)) {
POParser.throwWithLine('Previous string key comments (#|) are not supported', line);
}
// Flag comments
if (POParser.lineStartsWithPrefix(line, POParser.COMMENTS.FLAG)) {
entry = POParser.ensureEntry(entry);
const flagsText = line.substring(POParser.COMMENTS.FLAG.length).trim();
entry.flags = flagsText.split(',').map((flag)=>flag.trim()).filter(Boolean);
continue;
}
// Reference comments
if (POParser.lineStartsWithPrefix(line, POParser.COMMENTS.REFERENCE)) {
var _entry;
entry = POParser.ensureEntry(entry);
const parts = line.substring(POParser.COMMENTS.REFERENCE.length).trim().split(POParser.FILE_COLUMN_SEPARATOR);
const path = parts[0];
let lineNumber;
if (parts.length > 1) {
const parsedLine = parseInt(parts[1]);
if (!isNaN(parsedLine)) {
lineNumber = parsedLine;
}
}
(_entry = entry).references ?? (_entry.references = []);
const reference = {
path
};
if (lineNumber) {
reference.line = lineNumber;
}
entry.references.push(reference);
continue;
}
// Extracted comments
if (POParser.lineStartsWithPrefix(line, POParser.COMMENTS.EXTRACTED)) {
var _entry1;
entry = POParser.ensureEntry(entry);
const comment = line.substring(POParser.COMMENTS.EXTRACTED.length).trim();
(_entry1 = entry).extractedComments ?? (_entry1.extractedComments = []);
entry.extractedComments.push(comment);
continue;
}
// Check for unsupported features
if (POParser.lineStartsWithPrefix(line, POParser.KEYWORDS.MSGID_PLURAL)) {
POParser.throwWithLine('Plural forms (msgid_plural) are not supported, use ICU pluralization instead', line);
}
// msgctxt
if (POParser.lineStartsWithPrefix(line, POParser.KEYWORDS.MSGCTXT)) {
entry = POParser.ensureEntry(entry);
entry.msgctxt = POParser.unescape(POParser.extractQuotedString(line.substring(POParser.KEYWORDS.MSGCTXT.length + 1), state));
continue;
}
// msgid
if (POParser.lineStartsWithPrefix(line, POParser.KEYWORDS.MSGID)) {
entry = POParser.ensureEntry(entry);
entry.msgid = POParser.unescape(POParser.extractQuotedString(line.substring(POParser.KEYWORDS.MSGID.length + 1), state));
if (POParser.isMetaEntry(entry, messages)) {
state = 'meta';
entry = undefined;
}
continue;
}
// msgstr
if (POParser.lineStartsWithPrefix(line, POParser.KEYWORDS.MSGSTR)) {
entry = POParser.ensureEntry(entry);
entry.msgstr = POParser.unescape(POParser.extractQuotedString(line.substring(POParser.KEYWORDS.MSGSTR.length + 1), state));
if (POParser.isMetaEntry(entry, messages)) {
state = 'meta';
entry = undefined;
}
continue;
}
// Multi-line strings are not supported in entry mode
if (line.startsWith(POParser.QUOTE)) {
POParser.throwWithLine('Multi-line strings are not supported, use single-line strings instead', line);
}
}
}
// Finish any remaining entry
if (state === 'entry' && entry) {
messages.push(POParser.finishEntry(entry));
}
return {
meta: Object.keys(meta).length > 0 ? meta : undefined,
messages: messages.length > 0 ? messages : undefined
};
}
static isMetaEntry(entry, messages) {
return messages.length === 0 && entry.msgid === '' && entry.msgstr === '';
}
static serialize(catalog) {
const lines = [];
// Metadata
if (catalog.meta) {
lines.push(`${POParser.KEYWORDS.MSGID} ${POParser.QUOTE}${POParser.QUOTE}`);
lines.push(`${POParser.KEYWORDS.MSGSTR} ${POParser.QUOTE}${POParser.QUOTE}`);
for (const [key, value] of Object.entries(catalog.meta)){
lines.push(`${POParser.QUOTE}${key}${POParser.META_SEPARATOR} ${POParser.escape(value)}${POParser.NEWLINE}${POParser.QUOTE}`);
}
lines.push('');
}
// Messages
if (catalog.messages) {
for (const entry of catalog.messages){
if (entry.extractedComments && entry.extractedComments.length > 0) {
for (const comment of entry.extractedComments){
lines.push(`${POParser.COMMENTS.EXTRACTED} ${comment}`);
}
}
if (entry.references && entry.references.length > 0) {
for (const ref of entry.references){
let refString = ref.path;
if (ref.line) {
refString += `${POParser.FILE_COLUMN_SEPARATOR}${ref.line}`;
}
lines.push(`${POParser.COMMENTS.REFERENCE} ${refString}`);
}
}
if (entry.flags && entry.flags.length > 0) {
lines.push(`${POParser.COMMENTS.FLAG} ${entry.flags.join(POParser.FLAG_SEPARATOR)}`);
}
if (entry.msgctxt) {
lines.push(`${POParser.KEYWORDS.MSGCTXT} ${POParser.QUOTE}${POParser.escape(entry.msgctxt)}${POParser.QUOTE}`);
}
lines.push(`${POParser.KEYWORDS.MSGID} ${POParser.QUOTE}${POParser.escape(entry.msgid)}${POParser.QUOTE}`);
lines.push(`${POParser.KEYWORDS.MSGSTR} ${POParser.QUOTE}${POParser.escape(entry.msgstr)}${POParser.QUOTE}`);
lines.push('');
}
}
return lines.join('\n');
}
static lineStartsWithPrefix(line, prefix) {
return line.startsWith(prefix + ' ');
}
static throwWithLine(message, line) {
throw new Error(`${message}:\n> ${line}`);
}
static splitLines(content) {
// Avoid overhead for Unix newlines only
if (content.includes('\r')) {
content = content.replace(/\r\n/g, '\n');
}
return content.split('\n');
}
static ensureEntry(entry) {
return entry || {};
}
static finishEntry(entry) {
if (entry.msgid == null || entry.msgstr == null) {
throw new Error('Incomplete message entry: both msgid and msgstr are required');
}
return {
msgctxt: entry.msgctxt,
msgid: entry.msgid,
msgstr: entry.msgstr,
extractedComments: entry.extractedComments,
references: entry.references,
flags: entry.flags
};
}
static extractQuotedString(line, state) {
const trimmed = line.trim();
if (!trimmed.startsWith(POParser.QUOTE)) {
POParser.throwWithLine('Incomplete quoted string', line);
}
if (!trimmed.endsWith(POParser.QUOTE)) {
if (state === 'meta') {
return trimmed.substring(POParser.QUOTE.length);
}
POParser.throwWithLine('Incomplete quoted string', line);
}
const endIndex = trimmed.length - POParser.QUOTE.length;
return trimmed.substring(POParser.QUOTE.length, endIndex);
}
static escape(value) {
let result = '';
for (const char of value){
const mapped = POParser.ESCAPE_LOOKUP[char];
result += mapped != null ? `\\${mapped}` : char;
}
return result;
}
static unescape(value) {
let result = '';
for(let i = 0; i < value.length; i++){
const char = value[i];
if (char === '\\' && i + 1 < value.length) {
const nextChar = value[i + 1];
const mapped = POParser.UNESCAPE_LOOKUP[nextChar];
if (mapped != null) {
result += mapped;
i++;
continue;
}
}
result += char;
}
return result;
}
}
POParser.KEYWORDS = {
MSGID: 'msgid',
MSGSTR: 'msgstr',
MSGCTXT: 'msgctxt',
MSGID_PLURAL: 'msgid_plural'
};
POParser.COMMENTS = {
REFERENCE: '#:',
EXTRACTED: '#.',
TRANSLATOR: '#',
FLAG: '#,',
PREVIOUS: '#|'
};
POParser.QUOTE = '"';
POParser.NEWLINE = '\\n';
POParser.FILE_COLUMN_SEPARATOR = ':';
POParser.META_SEPARATOR = ':';
POParser.FLAG_SEPARATOR = ', ';
POParser.ESCAPE_LOOKUP = {
'\\': '\\',
'"': '"',
'\n': 'n',
'\r': 'r',
'\t': 't'
};
POParser.UNESCAPE_LOOKUP = Object.entries(POParser.ESCAPE_LOOKUP).reduce((acc, [char, code])=>{
acc[code] = char;
return acc;
}, {});
export { POParser as default };