Made parser much more robust. Now handles files without whitespace seps.

This commit is contained in:
Mike Holly 2012-01-02 15:52:05 -08:00
parent 1e13e78654
commit 69fcd1b40d
4 changed files with 495 additions and 154 deletions

192
lib/po.js
View File

@ -1,30 +1,45 @@
var fs = require('fs')
, util = require('util');
function trim(string) {
return string.replace(/^\s+|\s+$/g, '');
};
var PO = function() {
this.comments = [];
this.headers = {};
this.items = [];
};
PO.prototype.save = function(filename, callback) {
fs.writeFile(filename, this.toString(), function(err){
fs.writeFile(filename, this.toString(), function(err) {
if (err) throw err;
callback && callback();
});
};
PO.prototype.toString = function() {
var lines = ['msgid ""', 'msgstr ""']
var lines = []
, that = this;
if (this.comments) {
this.comments.forEach(function(comment) {
lines.push('# ' + comment);
});
lines.push('');
}
lines.push('msgid ""');
lines.push('msgstr ""');
var keys = Object.keys(this.headers);
keys.forEach(function(key){
keys.forEach(function(key) {
lines.push(util.format('"%s: %s\\n"', key, that.headers[key]));
});
lines.push('');
this.items.forEach(function(item){
this.items.forEach(function(item) {
lines.push(item.toString());
lines.push('');
});
@ -33,36 +48,112 @@ PO.prototype.toString = function() {
};
PO.load = function(filename, callback) {
fs.readFile(filename, 'utf-8', function(err, data){
fs.readFile(filename, 'utf-8', function(err, data) {
if (err) throw err;
var po = new PO
, parts = data.split(/\n\n/)
, headers = parts.shift().split(/\n/);
headers.forEach(function(header){
if (header.match(/^"/)) {
header = header.trim().replace(/^"/, '').replace(/\\n"$/, '');
var p = header.split(/:/)
, name = p.shift().trim()
, value = p.join(':').trim().replace(/n$/);
po.headers[name] = value;
}
});
parts.forEach(function(part){
if (part.length < 1) return;
var item = PO.Item.parse(part);
po.items.push(item);
});
var po = PO.parse(data);
callback && callback(po);
});
};
PO.parse = function(data) {
var po = new PO
, sections = data.split(/\n\n/)
, headers = sections.shift()
, lines = sections.join("\n").split(/\n/);
po.headers = {
'Project-Id-Version': '',
'Report-Msgid-Bugs-To': '',
'POT-Creation-Date': '',
'PO-Revision-Date': '',
'Last-Translator': '',
'Language-Team': '',
'Content-Type': '',
'Content-Transfer-Encoding': '',
'Plural-Forms': '',
};
headers.split(/\n/).forEach(function(header) {
if (header.match(/^#/)) {
po.comments.push(header.replace(/^#\s*/, ''));
}
if (header.match(/^"/)) {
header = header.trim().replace(/^"/, '').replace(/\\n"$/, '');
var p = header.split(/:/)
, name = p.shift().trim()
, value = p.join(':').trim().replace(/n$/);
po.headers[name] = value;
}
});
var item = new PO.Item()
, context = null
, plural = 0;
function finish() {
if (item.msgid.length > 0) {
po.items.push(item);
item = new PO.Item();
}
};
function extract(string) {
string = trim(string);
string = string.replace(/^[^"]*"|"$/g, '');
string = string.replace(/\\"/g, '"');
string = string.replace(/\\\\/g, '\\');
return string;
};
while (lines.length > 0) {
var line = trim(lines.shift())
, add = false;
if (line.match(/^#:/)) { // Reference
finish();
item.references.push(trim(line.replace(/^#:/, '')));
}
else if (line.match(/^#/)) { // Comment
finish();
item.comments.push(trim(line.replace(/^#/, '')));
}
else if (line.match(/^msgid_plural/)) { // Plural form
item.msgid_plural = extract(line);
context = 'msgid_plural';
}
else if (line.match(/^msgid/)) { // Original
finish();
item.msgid = extract(line);
context = 'msgid';
}
else if (line.match(/^msgstr/)) { // Translation
var m = line.match(/^msgstr\[(\d+)\]/);
plural = m && m[1] ? parseInt(m[1]) : 0;
item.msgstr[plural] = extract(line);
context = 'msgstr'
}
else { // Probably multiline string or blank
if (line.length > 0) {
if (context == 'msgstr') {
item.msgstr[plural] += extract(line);
}
else if (context == 'msgid') {
item.msgid += extract(line);
}
else if (context == 'msgid_plural') {
item.msgid_plural += extract(line);
}
}
}
};
finish();
return po;
};
PO.Item = function() {
this.msgid = null;
this.msgid = '';
this.references = [];
this.msgid_plural = null;
this.msgid_plural = '';
this.msgstr = [];
this.comments = [];
};
@ -71,13 +162,13 @@ PO.Item.prototype.toString = function() {
var lines = []
, that = this;
var _processEach = function(keyword, text, i) {
var _process = function(keyword, text, i) {
var lines = []
, parts = text.split(/\n/)
, index = typeof i != 'undefined' ? util.format('[%d]', i) : '';
if (parts.length > 1) {
lines.push(util.format('%s%s ""', keyword, index));
parts.forEach(function(part){
parts.forEach(function(part) {
lines.push(util.format('"%s"', part))
});
}
@ -88,20 +179,20 @@ PO.Item.prototype.toString = function() {
}
if (this.references.length > 0) {
this.references.forEach(function(ref){
this.references.forEach(function(ref) {
lines.push(util.format('#: %s', ref));
});
['msgid', 'msgid_plural', 'msgstr'].forEach(function(keyword){
['msgid', 'msgid_plural', 'msgstr'].forEach(function(keyword) {
var text = that[keyword];
if (text != null) {
if (util.isArray(text) && text.length > 1) {
text.forEach(function(t, i){
lines = lines.concat(_processEach(keyword, t, i));
text.forEach(function(t, i) {
lines = lines.concat(_process(keyword, t, i));
});
}
else {
text = util.isArray(text) ? text.join() : text;
lines = lines.concat(_processEach(keyword, text));
lines = lines.concat(_process(keyword, text));
}
}
});
@ -110,37 +201,4 @@ PO.Item.prototype.toString = function() {
return lines.join("\n");
};
PO.Item.parse = function(chunk) {
var item = new PO.Item();
var parts = chunk.split(/msg/);
var _extract = function(string) {
var lines = string.split(/\n/);
var value = '';
lines.forEach(function(line){
var p = line.split(/"/); p.shift(); p.pop();
value += "\n" + p.join('"');
});
return value.trim();
};
parts.forEach(function(part){
if (part.match(/^#:/)) {
item.references.push(part.replace(/^#:\s/, ''));
}
else if (part.match(/^id\s/)) {
item.msgid = _extract(part);
}
else if (part.match(/id_plural/)) {
item.msgid_plural = _extract(part);
}
else if (part.match(/str/)) {
item.msgstr.push(_extract(part));
}
});
return item;
};
module.exports = PO;

View File

@ -1,49 +1,150 @@
# French translation of Link (6.x-2.9)
# Copyright (c) 2011 by the French translation team
#
msgid ""
msgstr ""
"Project-Id-Version: PROJECT VERSION\n"
"POT-Creation-Date: 2011-08-12 09:55-0700\n"
"PO-Revision-Date: 2011-12-21 22:30-0800\n"
"Last-Translator: Mike Holly <mikejholly@gmail.com>\n"
"Language-Team: LANGUAGE <EMAIL@ADDRESS>\n"
"MIME-Version: 1.0\n"
"Project-Id-Version: Link (6.x-2.9)\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2011-12-31 23:39+0000\n"
"PO-Revision-Date: YYYY-mm-DD HH:MM+ZZZZ\n"
"Last-Translator: \n"
"Language-Team: French\n"
"Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=n>1;\n"
"Plural-Forms: nplurals=2; plural=(n!=1);\n"
"MIME-Version: 1.0\n"
#: strutta/strutta.admin.inc:11
msgid "CSV file"
msgstr "Archivo CVS"
#: strutta/strutta.admin.inc:12
msgid "Please make sure the file is formatted correctly (Name, E-mail)"
msgstr "Por favor asegúrese de que el archivo tenga el fomato correcto (Nombre, E-mail)"
#: strutta/strutta.admin.inc:18
msgid "From address"
msgstr "Dirección"
#: strutta/strutta.admin.inc:25
msgid "Plain text"
msgstr "Texto plano"
#: strutta/strutta.admin.inc:26
msgid "HTML markup"
msgstr "Formato HTML"
#: strutta/strutta.admin.inc:28
msgid "Content type"
msgstr "Tipo de contenido"
#: strutta_contests_client/strutta_contests_client.module:1381
msgid ""
"There is a limit of <strong>1 vote</strong> per entry over <strong>!period</strong>. \n"
" <br>Please come back and vote again in <strong>!retry</strong>."
msgstr ""
"Hay un límite de <strong>1 voto</strong> por entrada <strong>!period</strong>. \n"
" <br>Por favor vuelve más tarde para votar<strong>!retry</strong>.\""
#: strutta_submit/strutta_submit.module:113
msgid "@count entry"
msgid_plural "@count entries"
msgstr[0] "@count entrada"
msgstr[1] "@count entradas"

View File

@ -1,12 +1,11 @@
var po = require('../lib/po.js')
var PO = require('../lib/po.js')
, fs = require('fs')
, assert = require('assert')
po.load('text.po', function(_po){
_po.save('copy.po', function(){
var orig = fs.readFileSync('text.po');
var data = fs.readFileSync('copy.po');
assert.equal(orig, data, 'Saved data is identical to original.');
});
PO.load('text.po', function(po) {
assert.equal(po.headers['Plural-Forms'], 'nplurals=2; plural=(n!=1);', 'Parsed "Plural-Forms" header.');
assert.equal(po.items.length, 67, 'Successfully parsed 67 items.');
var item = po.items.pop();
assert.equal(item.comments.length, 1, 'Parsed item comment.');
});

View File

@ -1,49 +1,232 @@
# French translation of Link (6.x-2.9)
# Copyright (c) 2011 by the French translation team
#
msgid ""
msgstr ""
"Project-Id-Version: PROJECT VERSION\n"
"POT-Creation-Date: 2011-08-12 09:55-0700\n"
"PO-Revision-Date: 2011-12-21 22:30-0800\n"
"Last-Translator: Mike Holly <mikejholly@gmail.com>\n"
"Language-Team: LANGUAGE <EMAIL@ADDRESS>\n"
"Project-Id-Version: Link (6.x-2.9)\n"
"POT-Creation-Date: 2011-12-31 23:39+0000\n"
"PO-Revision-Date: YYYY-mm-DD HH:MM+ZZZZ\n"
"Language-Team: French\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=n>1;\n"
"Plural-Forms: nplurals=2; plural=(n!=1);\n"
#: strutta/strutta.admin.inc:11
msgid "CSV file"
msgstr "Archivo CVS"
#: strutta/strutta.admin.inc:12
msgid "Please make sure the file is formatted correctly (Name, E-mail)"
msgstr "Por favor asegúrese de que el archivo tenga el fomato correcto (Nombre, E-mail)"
#: strutta/strutta.admin.inc:18
msgid "From address"
msgstr "Dirección"
#: strutta/strutta.admin.inc:25
msgid "Plain text"
msgstr "Texto plano"
#: strutta/strutta.admin.inc:26
msgid "HTML markup"
msgstr "Formato HTML"
#: strutta/strutta.admin.inc:28
msgid "Content type"
msgstr "Tipo de contenido"
#: strutta_contests_client/strutta_contests_client.module:1381
msgid "Title"
msgstr "Titre"
msgid "CCK"
msgstr "CCK"
msgid "Content"
msgstr "Contenu"
msgid "Link"
msgstr "Lien"
msgid "Link Target"
msgstr "Cible du Lien"
msgid "URL"
msgstr "URL"
msgid "Placeholder tokens"
msgstr "Jetons (tokens) de substitution"
msgid ""
"There is a limit of <strong>1 vote</strong> per entry over <strong>!period</strong>. \n"
" <br>Please come back and vote again in <strong>!retry</strong>."
"The following placeholder tokens can be used in both paths and titles. "
"When used in a path or title, they will be replaced with the "
"appropriate values."
msgstr ""
"Hay un límite de <strong>1 voto</strong> por entrada <strong>!period</strong>. \n"
" <br>Por favor vuelve más tarde para votar<strong>!retry</strong>.\""
#: strutta_submit/strutta_submit.module:113
msgid "@count entry"
msgid_plural "@count entries"
msgstr[0] "@count entrada"
msgstr[1] "@count entradas"
"Les ébauches de jetons suivantes peuvent être utilisées à la fois "
"dans les chemins et dans les titres. Lorsqu'elles sont utilisées dans "
"un chemin ou un titre, elles seront remplacées par les valeurs "
"appropriées."
msgid "Optional Title"
msgstr "Titre Optionnel"
msgid "Required Title"
msgstr "Titre Obligatoire"
msgid "No Title"
msgstr "Aucun Titre"
msgid "Link Title"
msgstr "Titre du Lien"
msgid "URL Display Cutoff"
msgstr "Coupure de l'Affichage de l'URL"
msgid ""
"If the user does not include a title for this link, the URL will be "
"used as the title. When should the link title be trimmed and finished "
"with an elipsis (&hellip;)? Leave blank for no limit."
msgstr ""
"Si l'utilisateur n'inclue pas de titre pour ce lien, l'URL sera "
"utilisée en tant que titre. A quel endroit le lien devra-t-il être "
"coupé et terminé par une ellipse (&hellip;) ? Laissez vide pour "
"aucune limite."
msgid "Default (no target attribute)"
msgstr "Par Défaut (aucun attribut de cible)"
msgid "Open link in window root"
msgstr "Ouvrir le lien dans la fenêtre courante"
msgid "Open link in new window"
msgstr "Ouvrir le lien dans une nouvelle fenêtre"
msgid "Allow the user to choose"
msgstr "Autoriser l'utilisateur à choisir"
msgid "Additional CSS Class"
msgstr "Classe CSS additionnelle"
msgid "Not a valid URL."
msgstr "Cette URL n'est pas valide."
msgid "Titles are required for all links."
msgstr "Les titres sont obligatoires pour tous les liens."
msgid "Open URL in a New Window"
msgstr "Ouvril l'URL dans une Nouvelle Fenêtre"
msgid "Defines simple link field types."
msgstr "Définit les types de champs \"lien simple\"."
msgid "Link URL"
msgstr "Url du Lien"
msgid "Wildcard"
msgstr "Joker"
msgid "="
msgstr "="
msgid "Protocol"
msgstr "Protocole"
msgid "Optional URL"
msgstr "URL optionnelle"
msgid "Static Title: "
msgstr "Titre Statique : "
msgid ""
"If the link title is optional or required, a field will be displayed "
"to the end user. If the link title is static, the link will always use "
"the same title. If <a href=\"http://drupal.org/project/token\">token "
"module</a> is installed, the static title value may use any other node "
"field as its value. Static and token-based titles may include most "
"inline XHTML tags such as <em>strong</em>, <em>em</em>, <em>img</em>, "
"<em>span</em>, etc."
msgstr ""
"Si le titre du lien est facultatif ou obligatoire, un champ sera "
"affiché à l'utilisateur final. Si le titre du lien est statique, le "
"lien utilisera toujours le même titre. Si le <a "
"href=\"http://drupal.org/project/token\">module token</a> est "
"installé, le titre statique peut utiliser n'importe quel autre champ "
"du nœud pour sa valeur. Les titres statiques et basés sur des jetons "
"(tokens) peuvent contenir la plupart des balises XHTML en ligne, "
"telles que <em>strong</em>, <em>em</em>, <em>img</em>, <em>span</em>, "
"etc."
msgid "Allow user-entered tokens"
msgstr "Autoriser les jetons (tokens) saisis par l'utilisateur"
msgid ""
"Checking will allow users to enter tokens in URLs and Titles on the "
"node edit form. This does not affect the field settings on this page."
msgstr ""
"Le fait de cocher cette case permettra aux utilisateurs de saisir des "
"jetons dans les URL et les Titres dans le formulaire d'édition du "
"nœud. Ceci n'affecte pas les configurations de champ sur cette page."
msgid "Rel Attribute"
msgstr "Attribut Rel"
msgid ""
"When output, this link will have this rel attribute. The most common "
"usage is <a "
"href=\"http://en.wikipedia.org/wiki/Nofollow\">rel=&quot;nofollow&quot;</a> "
"which prevents some search engines from spidering entered links."
msgstr ""
"Quand il sera affiché, ce lien aura cet attribut rel. L'usage le plus "
"commun est <a "
"href=\"http://fr.wikipedia.org/wiki/Nofollow\">rel=&quot;nofollow&quot;</a> "
"qui empêche certains moteurs de recherche d'aspirer les liens suivis."
msgid "A default title must be provided if the title is a static value"
msgstr ""
"Un titre pas défaut doit être fourni si le titre est une valeur "
"statique"
msgid "At least one title or URL must be entered."
msgstr "Vous devez saisir au moins un titre ou une URL."
msgid "You cannot enter a title without a link url."
msgstr "Vous ne pouvez pas saisir un titre dans une url de lien."
msgid "Title, as link (default)"
msgstr "Titre, en tant que lien (par défaut)"
msgid "URL, as link"
msgstr "URL, en tant que lien"
msgid "Short, as link with title \"Link\""
msgstr "Court, comme lien avec le titre \"Lien\""
msgid "Label, as link with label as title"
msgstr "Étiquette, comme lien avec l'étiquette comme titre"
msgid "Separate title and URL"
msgstr "Titre et URL séparés"
msgid "Validator"
msgstr "Validateur"
msgid "Is one of"
msgstr "Fait partie de"
msgid "@label title"
msgstr "Titre de @label"
msgid "@label protocol"
msgstr "Protocole de @label"
msgid "@label target"
msgstr "Cible de @label"
msgid ""
"The title to use when this argument is present; it will override the "
"title of the view and titles from previous arguments. You can use "
"percent substitution here to replace with argument titles. Use \"%1\" "
"for the first argument, \"%2\" for the second, etc."
msgstr ""
"Le titre à utiliser lorsque cet argument est présent ; il écrasera "
"le titre de la vue et les titres provenant des arguments précédents. "
"Vous pouvez utiliser ici les substitutions de pourcentage pour "
"remplacer avec les titres des arguments. Utilisez \"%1\" pour le "
"premier argument, \"%2\" pour le second, etc."
msgid "Action to take if argument is not present"
msgstr "Action à mener si l'argument est absent"
msgid ""
"If this value is received as an argument, the argument will be "
"ignored; i.e, \"all values\""
msgstr ""
"Si cette valeur est reçue comme argument, l'argument sera ignoré ; "
"correspond à \"toutes les valeurs\""
msgid "Wildcard title"
msgstr "Titre du joker"
msgid "The title to use for the wildcard in substitutions elsewhere."
msgstr ""
"Le titre à utiliser pour le joker dans les substitutions partout "
"ailleurs."
msgid "<Basic validation>"
msgstr "<Validation basique>"
msgid "Action to take if argument does not validate"
msgstr "Actions à mener si l'argument ne passe pas la validation"
msgid ""
"The protocols displayed here are those globally available. You may add "
"more protocols by modifying the <em>filter_allowed_protocols</em> "
"variable in your installation."
msgstr ""
"Les protocoles affichés ici sont ceux disponibles de manière "
"globale. Vous pouvez ajouter plus de protocoles en modifiant la "
"variable <em>filter_allowed_protocols</em> de votre installation."
msgid "Link title"
msgstr "Titre du lien"
msgid "exposed"
msgstr "exposé"
msgid "Formatted html link"
msgstr "Lien html formaté"
msgid ""
"Store a title, href, and attributes in the database to assemble a "
"link."
msgstr ""
"Stocker un titre, href et des attributs dans la base de données pour "
"les assembler dans un lien."
msgid "URL, as plain text"
msgstr "URL, texte simple"
msgid "@label URL"
msgstr "URL de @label"
msgid "Validate URL"
msgstr "Valider l'URL"
msgid ""
"If checked, the URL field will be verified as a valid URL during "
"validation."
msgstr ""
"Si coché, la validité du format de l'URL sera verifiée durant la "
"validation."
msgid ""
"If checked, the URL field is optional and submitting a title alone "
"will be acceptable. If the URL is omitted, the title will be displayed "
"as plain text."
msgstr ""
"Si coché, le champ URL est optionnel and soumettre un titre seul sera "
"accepté. SI l'URL est omise, le titre sera affiché en texte brut."
msgid ""
"When output, this link will have this class attribute. Multiple "
"classes should be separated by spaces."
msgstr ""
"Lors de l'affichage, le lien aura cet attribut de classe (class). Les "
"classes doivent être séparées par des espaces."
msgid "Link 'title' Attribute"
msgstr "Attribut 'title' du lien"
# Comment
msgid "Title, as plain text"
msgstr "Attribut title, en tant que texte brut"