]> git.lyx.org Git - lyx.git/blob - src/support/Messages.cpp
ca41d6a13dbcacd5046c80227044fc0a6614fac9
[lyx.git] / src / support / Messages.cpp
1 /* \file Messages.cpp
2  * This file is part of LyX, the document processor.
3  * Licence details can be found in the file COPYING.
4  *
5  * \author Lars Gullik Bjønnes
6  *
7  * Full author contact details are available in file CREDITS.
8  */
9
10 /*
11   This is a limited parser for gettext's po files. Several features are
12   not handled for now:
13    * encoding is supposed to be UTF-8 (the charset parameter is not honored)
14    * context is not handled (implemented differently in LyX)
15    * plural forms not implemented (not used for now in LyX).
16    * The byte endianness of the machine on which the .mo file have been
17      built is expected to be the same as the one of the machine where this
18      code is run.
19
20   The data is loaded in a std::map object for simplicity.
21  */
22
23 /*
24   Format of a MO file. Source: http://www.gnu.org/software/gettext/manual/html_node/MO-Files.html
25
26              byte
27                   +------------------------------------------+
28                0  | magic number = 0x950412de                |
29                   |                                          |
30                4  | file format revision = 0                 |
31                   |                                          |
32                8  | number of strings                        |  == N
33                   |                                          |
34               12  | offset of table with original strings    |  == O
35                   |                                          |
36               16  | offset of table with translation strings |  == T
37                   |                                          |
38               20  | size of hashing table                    |  == S
39                   |                                          |
40               24  | offset of hashing table                  |  == H
41                   |                                          |
42                   .                                          .
43                   .    (possibly more entries later)         .
44                   .                                          .
45                   |                                          |
46                O  | length & offset 0th string  ----------------.
47            O + 8  | length & offset 1st string  ------------------.
48                    ...                                    ...   | |
49      O + ((N-1)*8)| length & offset (N-1)th string           |  | |
50                   |                                          |  | |
51                T  | length & offset 0th translation  ---------------.
52            T + 8  | length & offset 1st translation  -----------------.
53                    ...                                    ...   | | | |
54      T + ((N-1)*8)| length & offset (N-1)th translation      |  | | | |
55                   |                                          |  | | | |
56                H  | start hash table                         |  | | | |
57                    ...                                    ...   | | | |
58        H + S * 4  | end hash table                           |  | | | |
59                   |                                          |  | | | |
60                   | NUL terminated 0th string  <----------------' | | |
61                   |                                          |    | | |
62                   | NUL terminated 1st string  <------------------' | |
63                   |                                          |      | |
64                    ...                                    ...       | |
65                   |                                          |      | |
66                   | NUL terminated 0th translation  <---------------' |
67                   |                                          |        |
68                   | NUL terminated 1st translation  <-----------------'
69                   |                                          |
70                    ...                                    ...
71                   |                                          |
72                   +------------------------------------------+
73
74  */
75
76 #include <config.h>
77
78 #include "support/Messages.h"
79
80 #include "support/debug.h"
81 #include "support/docstring.h"
82 #include "support/lstrings.h"
83 #include "support/Package.h"
84 #include "support/unicode.h"
85
86 #include "support/lassert.h"
87
88 #include <boost/cstdint.hpp>
89
90 #include <cerrno>
91 #include <fstream>
92
93 #ifdef HAVE_SYS_STAT_H
94 # include <sys/stat.h>
95 #endif
96
97 using namespace std;
98 using boost::uint32_t;
99
100 namespace lyx {
101
102 void cleanTranslation(docstring & trans)
103 {
104         /*
105           Some english words have different translations, depending on
106           context. In these cases the original string is augmented by
107           context information (e.g. "To:[[as in 'From page x to page
108           y']]" and "To:[[as in 'From format x to format y']]". Also, 
109           when placeholders are used, the context can indicate what will
110           be substituted for the placeholder (e.g. "%1$s[[date]], %1$s
111           [[time]]). This means that we need to filter out everything 
112           in double square brackets at the end of the string, otherwise
113           the user sees bogus messages. If we are unable to honour the
114           request we just return what we got in.
115         */
116         static docstring const ctx_start = from_ascii("[[");
117         static docstring const ctx_end = from_ascii("]]");
118         while (true) {
119                 size_t const pos1 = trans.find(ctx_start);
120                 if (pos1 != docstring::npos) {
121                         size_t const pos2 = trans.find(ctx_end, pos1);
122                         if (pos2 != docstring::npos) {
123                                 trans.erase(pos1, pos2 - pos1 + 2);
124                                 continue;
125                         }
126                 }
127                 break;
128         }
129 }
130
131 } // lyx
132
133
134 #ifdef ENABLE_NLS
135
136 using namespace lyx::support;
137
138 namespace lyx {
139
140 std::string Messages::gui_lang_;
141
142
143 // This version use the traditional gettext.
144 Messages::Messages(string const & l)
145         : lang_(l)
146 {
147         // strip off any encoding suffix, i.e., assume 8-bit po files
148         size_t i = lang_.find(".");
149         lang_ = lang_.substr(0, i);
150         LYXERR(Debug::LOCALE, "language(" << lang_ << ")");
151
152         readMoFile();
153 }
154
155
156 namespace {
157
158 // Find the code we have for a given language code. Return empty if not found.
159 string realCode(string const & c)
160 {
161         // Qt tries to outsmart us and transforms en_US to C.
162         string code = (c == "C") ? "en" : c;
163         // this loops at most twice
164         while (true) {
165                 if (package().messages_file(code).isReadableFile())
166                         return code;
167                 if (contains(code, '_'))
168                         code = token(code, '_', 0);
169                 else
170                         break;
171         }
172         return string();
173 }
174 }
175
176
177 bool Messages::available(string const & c)
178 {
179         return !realCode(c).empty();
180 }
181
182
183 string Messages::language() const
184 {
185         return realCode(lang_);
186 }
187
188
189 struct MoHeader
190 {
191         // magic number = 0x950412de
192         uint32_t magic;
193         // file format revision = 0
194         uint32_t rev;
195         // number of strings
196         uint32_t N;
197         // offset of table with original strings
198         uint32_t O;
199         // offset of table with translation strings
200         uint32_t T;
201         // there is a hashing table afterwrds, but we ignore it
202 };
203
204
205 struct StringTable
206 {
207         // string length
208         uint32_t length;
209         // string offset
210         uint32_t offset;
211 };
212
213
214 bool Messages::readMoFile()
215 {
216         // FIXME:remove
217         if (lang_.empty()) {
218                 LYXERR0("No language given, nothing to load.");
219                 return false;
220         }
221
222         string const code = realCode(lang_);
223         if (code.empty()) {
224                 LYXERR0("Cannot find translation for language " << lang_);
225                 return false;
226         }
227
228         string const filen = package().messages_file(code).toSafeFilesystemEncoding();
229
230         // get file size
231         struct stat buf;
232         if (stat(filen.c_str(), &buf)) {
233                 LYXERR0("Cannot get information for file " << filen);
234                 return false;
235         }
236
237         vector<char> moData(buf.st_size);
238
239         ifstream is(filen.c_str(), ios::in | ios::binary);
240         if (!is.read(&moData[0], buf.st_size)) {
241                 LYXERR0("Cannot read file " << filen);
242                 return false;
243         }
244
245         MoHeader const * header = reinterpret_cast<MoHeader const *>(&moData[0]);
246         if (header->magic != 0x950412de) {
247                 LYXERR0("Wrong magic number for file " << filen
248                         << ".\nExpected 0x950412de, got " << std::hex << header->magic);
249                 return false;
250         }
251
252         StringTable const * orig = reinterpret_cast<StringTable const *>(&moData[0] + header->O);
253         StringTable const * trans = reinterpret_cast<StringTable const *>(&moData[0] + header->T);
254         // First the header
255         string const info = string(&moData[0] + trans[0].offset, trans[0].length);
256         size_t pos = info.find("charset=");
257         if (pos != string::npos) {
258                 pos += 8;
259                 string charset;
260                 size_t pos2 = info.find("\n", pos);
261                 if (pos2 == string::npos)
262                         charset = info.substr(pos);
263                 else
264                         charset = info.substr(pos, pos2 - pos);
265                 charset = ascii_lowercase(trim(charset));
266                 if (charset != "utf-8") {
267                         LYXERR0("Wrong encoding " << charset << " for file " << filen);
268                         return false;
269                 }
270         } else {
271                 LYXERR0("Cannot find encoding encoding for file " << filen);
272                 return false;
273         }
274
275         for (size_t i = 1; i < header->N; ++i) {
276                 // Note that in theory the strings may contain NUL characters.
277                 // This may be the case with plural forms
278                 string const ostr(&moData[0] + orig[i].offset, orig[i].length);
279                 docstring tstr = from_utf8(string(&moData[0] + trans[i].offset,
280                                                   trans[i].length));
281                 cleanTranslation(tstr);
282                 trans_map_[ostr] = tstr;
283                 //lyxerr << ostr << " ==> " << tstr << endl;
284         }
285
286         return true;
287 }
288
289 docstring const Messages::get(string const & m) const
290 {
291         if (m.empty())
292                 return docstring();
293
294         TranslationMap::const_iterator it = trans_map_.find(m);
295         if (it != trans_map_.end())
296                 return it->second;
297         else {
298                 docstring res = from_utf8(m);
299                 cleanTranslation(res);
300                 return res;
301         }
302 }
303
304 } // namespace lyx
305
306 #else // ENABLE_NLS
307 // This is the dummy variant.
308
309 namespace lyx {
310
311 Messages::Messages(string const & /* l */) {}
312
313 docstring const Messages::get(string const & m) const
314 {
315         docstring trans = from_ascii(m);
316         cleanTranslation(trans);
317         return trans;
318 }
319
320 std::string Messages::language() const
321     {
322         return string();
323     }
324
325 bool Messages::available(string const & /* c */)
326 {
327         return false;
328 }
329
330 } // namespace lyx
331
332 #endif