Vidalia 0.3.1
ts2po.cpp
Go to the documentation of this file.
1/*
2** This file is part of Vidalia, and is subject to the license terms in the
3** LICENSE file, found in the top level directory of this distribution. If you
4** did not receive the LICENSE file with this file, you may obtain it from the
5** Vidalia source package distributed by the Vidalia Project at
6** http://www.torproject.org/projects/vidalia.html. No part of Vidalia,
7** including this file, may be copied, modified, propagated, or distributed
8** except according to the terms described in the LICENSE file.
9*/
10
11#include <QFile>
12#include <QFileInfo>
13#include <QDomDocument>
14#include <QTextStream>
15#include <QTextCodec>
16#include <QDateTime>
17#include <stdlib.h>
18
19#include "ts2po_config.h"
20
21#define TS_DOCTYPE "TS"
22#define TS_ELEMENT_CONTEXT "context"
23#define TS_ELEMENT_NAME "name"
24#define TS_ELEMENT_MESSAGE "message"
25#define TS_ELEMENT_SOURCE "source"
26#define TS_ELEMENT_TRANSLATION "translation"
27#define TS_ELEMENT_LOCATION "location"
28#define TS_ATTR_FILENAME "filename"
29#define TS_ATTR_LINE "line"
30
31
32/** Return the current time (in UTC) in the format YYYY-MM-DD HH:MM+0000. */
33QString
35{
36 QDateTime now = QDateTime::currentDateTime().toUTC();
37 return now.toString("yyyy-MM-dd hh:mm+0000");
38}
39
40/** Return a header to be placed at the top of the .po file. The header will
41 * include <b>encoding</b> in the Content-Type header line. */
42QString
43create_po_header(const QString &encoding)
44{
45 QString header;
46 QString tstamp = create_po_timestamp();
47
48 header.append("msgid \"\"\n");
49 header.append("msgstr \"\"\n");
50 header.append("\"Project-Id-Version: "TS2PO_PROJECT_ID"\\n\"\n");
51 header.append("\"Report-Msgid-Bugs-To: "TS2PO_CONTACT_ADDR"\\n\"\n");
52 header.append(QString("\"POT-Creation-Date: %1\\n\"\n").arg(tstamp));
53 header.append("\"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\\n\"\n");
54 header.append("\"Last-Translator: \\n\"\n");
55 header.append("\"Language-Team: "TS2PO_LANGUAGE_TEAM"\\n\"\n");
56 header.append("\"MIME-Version: 1.0\\n\"\n");
57 header.append("\"Content-Type: text/plain; ");
58 header.append(QString("charset=%1\\n\"\n").arg(encoding));
59 header.append("\"Content-Transfer-Encoding: 8bit\\n\"\n");
60 header.append("\"Plural-Forms: nplurals=INTEGER; plural=EXPRESSION;\\n\"\n");
61 header.append("\"X-Generator: Vidalia ts2po "TS2PO_VERSION"\\n\"\n");
62 header.append("\n");
63
64 return header;
65}
66
67/** Parse the filename from the relative or absolute path given in
68 * <b>filePath</b>. */
69QString
70parse_filename(const QString &filePath)
71{
72 QFileInfo file(filePath);
73 return file.fileName();
74}
75
76/** Convert the messages in <b>context</b> to PO format. The output will be
77 * appended to <b>po</b>. Returns the number of source messages converted on
78 * success, or -1 on error and <b>errorMessage</b> will be set. */
79int
80convert_context(const QDomElement &context, QString *po, QString *errorMessage)
81{
82 QString msgctxt, msgid, msgstr;
83 QString filename, line;
84 QDomElement location, source, translation;
85 int n = 0;
86
87 Q_ASSERT(po);
88 Q_ASSERT(errorMessage);
89
90 QDomElement name = context.firstChildElement(TS_ELEMENT_NAME);
91 if (name.isNull()) {
92 *errorMessage = QString("context element with no name (line %1)")
93 .arg(context.lineNumber());
94 return -1;
95 }
96 msgctxt = name.text();
97
98 QDomElement msg = context.firstChildElement(TS_ELEMENT_MESSAGE);
99 while (!msg.isNull()) {
100 /* Extract the <source> tags */
101 source = msg.firstChildElement(TS_ELEMENT_SOURCE);
102 if (source.isNull()) {
103 *errorMessage = QString("message element with no source string "
104 "(line %1)").arg(msg.lineNumber());
105 return -1;
106 }
107 msgid = source.text().trimmed();
108 msgid.replace("\r", "");
109 msgid.replace("\"", "\\\"");
110 msgid.replace("\n", "\\n\"\n\"");
111
112 /* Extract the <translation> tags */
113 translation = msg.firstChildElement(TS_ELEMENT_TRANSLATION);
114 msgstr = translation.text().trimmed();
115 msgstr.replace("\r", "");
116 msgstr.replace("\"", "\\\"");
117 msgstr.replace("\n", "\\n\"\n\"");
118
119 /* Try to extract the <location> tags (optional) */
120 location = msg.firstChildElement(TS_ELEMENT_LOCATION);
121 filename = parse_filename(location.attribute(TS_ATTR_FILENAME));
122 line = location.attribute(TS_ATTR_LINE);
123
124 /* Format the .po entry for this string */
125 if (!filename.isEmpty() && !line.isEmpty())
126 (*po).append(QString("#: %1:%2\n").arg(filename).arg(line));
127 (*po).append(QString("msgctxt \"%1\"\n").arg(msgctxt));
128 (*po).append(QString("msgid \"%1\"\n").arg(msgid));
129 (*po).append(QString("msgstr \"%1\"\n").arg(msgstr));
130 (*po).append("\n");
131
132 /* Find the next source message in the current context */
133 msg = msg.nextSiblingElement(TS_ELEMENT_MESSAGE);
134 n++;
135 }
136 return n;
137}
138
139/** Convert the TS-formatted document in <b>ts</b> to a PO-formatted document.
140 * The output will be written to <b>po</b>, including a file header that
141 * specifies <b>encoding</b> as the character set. Returns the number of strings
142 * converted on success, or -1 on error and <b>errorMessage</b> will be set. */
143int
144ts2po(const QDomDocument *ts, QString *po, const QString &encoding,
145 QString *errorMessage)
146{
147 int n_strings = 0;
148 QString context;
149
150 Q_ASSERT(ts);
151 Q_ASSERT(po);
152 Q_ASSERT(errorMessage);
153
154 /* Get the document root and check that it's valid */
155 QDomElement root = ts->documentElement();
156 if (root.tagName() != TS_DOCTYPE)
157 return -1;
158
159 /* Start with the PO header */
160 *po = create_po_header(encoding);
161
162 /* Iterate through all of the translation contexts and build up the PO file
163 * output. */
164 QDomElement child = root.firstChildElement(TS_ELEMENT_CONTEXT);
165 while (!child.isNull()) {
166 QString context;
167
168 /* Convert the current .ts context to .po */
169 int n = convert_context(child, &context, errorMessage);
170 if (n < 0)
171 return -1;
172
173 /* Add it to the output file */
174 (*po).append(context);
175 n_strings += n;
176
177 /* Move to the next context */
178 child = child.nextSiblingElement(TS_ELEMENT_CONTEXT);
179 }
180 return n_strings;
181}
182
183/** Display application usage and exit. */
184void
186{
187 QTextStream error(stderr);
188 error << "usage: ts2po [-q] -i <infile.ts> -o <outfile.po> "
189 "[-c <encoding>]\n";
190 error << " -q (optional) Quiet mode (errors are still displayed)\n";
191 error << " -i <infile.ts> Input .ts file\n";
192 error << " -o <outfile.po> Output .po file\n";
193 error << " -c <encoding> Text encoding (default: utf-8)\n";
194 error.flush();
195 exit(1);
196}
197
198int
199main(int argc, char *argv[])
200{
201 QTextStream error(stderr);
202 QString errorMessage;
203 char *infile, *outfile;
204 QTextCodec *codec = QTextCodec::codecForName("utf-8");
205 bool quiet = false;
206
207 /* Check for the correct number of input parameters. */
208 if (argc < 5 || argc > 8)
210 for (int i = 1; i < argc; i++) {
211 QString arg(argv[i]);
212 if (!arg.compare("-q", Qt::CaseInsensitive))
213 quiet = true;
214 else if (!arg.compare("-i", Qt::CaseInsensitive) && ++i < argc)
215 infile = argv[i];
216 else if (!arg.compare("-o", Qt::CaseInsensitive) && ++i < argc)
217 outfile = argv[i];
218 else if (!arg.compare("-c", Qt::CaseInsensitive) && ++i < argc) {
219 codec = QTextCodec::codecForName(argv[i]);
220 if (!codec) {
221 error << "Invalid text encoding specified.\n";
222 return 1;
223 }
224 } else
226 }
227
228 /* Read and parse the input .ts file. */
229 QDomDocument ts;
230 QFile tsFile(infile);
231 if (!ts.setContent(&tsFile, true, &errorMessage)) {
232 error << QString("Unable to parse '%1': %2\n").arg(infile)
233 .arg(errorMessage);
234 return 1;
235 }
236
237 /* Try to open the output .po file for writing. */
238 QFile poFile(outfile);
239 if (!poFile.open(QIODevice::WriteOnly | QIODevice::Text)) {
240 error << QString("Unable to open '%1' for writing: %2\n")
241 .arg(outfile)
242 .arg(tsFile.errorString());
243 return 2;
244 }
245
246 /* Convert the input .ts file to a .po formatted file. */
247 QString po;
248 int n_strings = ts2po(&ts, &po, QString(codec->name()), &errorMessage);
249 if (n_strings < 0) {
250 error << QString("Unable to convert '%1' to '%2': %3\n").arg(infile)
251 .arg(outfile)
252 .arg(errorMessage);
253 return 3;
254 }
255
256 /* Write the .po output. */
257 QTextStream out(&poFile);
258 out.setCodec(codec);
259 out << po;
260 poFile.close();
261
262 if (!quiet) {
263 QTextStream results(stdout);
264 results << QString("Converted %1 strings from %2 to %3.\n").arg(n_strings)
265 .arg(infile)
266 .arg(outfile);
267 }
268 return 0;
269}
270
DebugMessage arg(const QString &a)
Definition: tcglobal.h:48
QString i(QString str)
Definition: html.cpp:32
DebugMessage error(const QString &fmt)
Definition: tcglobal.cpp:40
void print_usage_and_exit()
Definition: ts2po.cpp:185
#define TS_ELEMENT_MESSAGE
Definition: ts2po.cpp:24
int main(int argc, char *argv[])
Definition: ts2po.cpp:199
int convert_context(const QDomElement &context, QString *po, QString *errorMessage)
Definition: ts2po.cpp:80
QString create_po_header(const QString &encoding)
Definition: ts2po.cpp:43
QString create_po_timestamp()
Definition: ts2po.cpp:34
#define TS_ELEMENT_SOURCE
Definition: ts2po.cpp:25
QString parse_filename(const QString &filePath)
Definition: ts2po.cpp:70
#define TS_ELEMENT_NAME
Definition: ts2po.cpp:23
#define TS_ATTR_FILENAME
Definition: ts2po.cpp:28
#define TS_DOCTYPE
Definition: ts2po.cpp:21
#define TS_ELEMENT_TRANSLATION
Definition: ts2po.cpp:26
#define TS_ELEMENT_CONTEXT
Definition: ts2po.cpp:22
#define TS_ATTR_LINE
Definition: ts2po.cpp:29
#define TS_ELEMENT_LOCATION
Definition: ts2po.cpp:27
int ts2po(const QDomDocument *ts, QString *po, const QString &encoding, QString *errorMessage)
Definition: ts2po.cpp:144
#define TS2PO_CONTACT_ADDR
Definition: ts2po_config.h:18
#define TS2PO_LANGUAGE_TEAM
Definition: ts2po_config.h:20
#define TS2PO_PROJECT_ID
Definition: ts2po_config.h:16
#define TS2PO_VERSION
Definition: ts2po_config.h:14