]> git.lyx.org Git - lyx.git/blob - src/support/Messages.cpp
cmake: Use provided gmo-files if platform lacks python or gettext tools.
[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 Messages::Messages(string const & l)
144         : lang_(l)
145 {
146         // strip off any encoding suffix, i.e., assume 8-bit po files
147         size_t i = lang_.find(".");
148         lang_ = lang_.substr(0, i);
149         LYXERR(Debug::LOCALE, "language(" << lang_ << ")");
150
151         readMoFile();
152 }
153
154
155 namespace {
156
157 // Find the code we have for a given language code. Return empty if not found.
158 string realCode(string const & c)
159 {
160         // Qt tries to outsmart us and transforms en_US to C.
161         string code = (c == "C") ? "en" : c;
162         // this loops at most twice
163         while (true) {
164                 if (package().messages_file(code).isReadableFile())
165                         return code;
166                 if (contains(code, '_'))
167                         code = token(code, '_', 0);
168                 else
169                         break;
170         }
171         return string();
172 }
173 }
174
175
176 bool Messages::available(string const & c)
177 {
178         return !realCode(c).empty();
179 }
180
181
182 string Messages::language() const
183 {
184         return realCode(lang_);
185 }
186
187
188 struct MoHeader
189 {
190         // magic number = 0x950412de
191         uint32_t magic;
192         // file format revision = 0
193         uint32_t rev;
194         // number of strings
195         uint32_t N;
196         // offset of table with original strings
197         uint32_t O;
198         // offset of table with translation strings
199         uint32_t T;
200         // there is a hashing table afterwrds, but we ignore it
201 };
202
203
204 struct StringTable
205 {
206         // string length
207         uint32_t length;
208         // string offset
209         uint32_t offset;
210 };
211
212
213 bool Messages::readMoFile()
214 {
215         // FIXME:remove
216         if (lang_.empty()) {
217                 LYXERR0("No language given, nothing to load.");
218                 return false;
219         }
220
221         string const code = realCode(lang_);
222         if (code.empty()) {
223                 LYXERR0("Cannot find translation for language " << lang_);
224                 return false;
225         }
226
227         string const filen = package().messages_file(code).toSafeFilesystemEncoding();
228
229         // get file size
230         struct stat buf;
231         if (stat(filen.c_str(), &buf)) {
232                 LYXERR0("Cannot get information for file " << filen);
233                 return false;
234         }
235
236         vector<char> moData(buf.st_size);
237
238         ifstream is(filen.c_str(), ios::in | ios::binary);
239         if (!is.read(&moData[0], buf.st_size)) {
240                 LYXERR0("Cannot read file " << filen);
241                 return false;
242         }
243
244         MoHeader const * header = reinterpret_cast<MoHeader const *>(&moData[0]);
245         if (header->magic != 0x950412de) {
246                 LYXERR0("Wrong magic number for file " << filen
247                         << ".\nExpected 0x950412de, got " << std::hex << header->magic);
248                 return false;
249         }
250
251         StringTable const * orig = reinterpret_cast<StringTable const *>(&moData[0] + header->O);
252         StringTable const * trans = reinterpret_cast<StringTable const *>(&moData[0] + header->T);
253         // First the header
254         string const info = string(&moData[0] + trans[0].offset, trans[0].length);
255         size_t pos = info.find("charset=");
256         if (pos != string::npos) {
257                 pos += 8;
258                 string charset;
259                 size_t pos2 = info.find("\n", pos);
260                 if (pos2 == string::npos)
261                         charset = info.substr(pos);
262                 else
263                         charset = info.substr(pos, pos2 - pos);
264                 charset = ascii_lowercase(trim(charset));
265                 if (charset != "utf-8") {
266                         LYXERR0("Wrong encoding " << charset << " for file " << filen);
267                         return false;
268                 }
269         } else {
270                 LYXERR0("Cannot find encoding encoding for file " << filen);
271                 return false;
272         }
273
274         for (size_t i = 1; i < header->N; ++i) {
275                 // Note that in theory the strings may contain NUL characters.
276                 // This may be the case with plural forms
277                 string const ostr(&moData[0] + orig[i].offset, orig[i].length);
278                 docstring tstr = from_utf8(string(&moData[0] + trans[i].offset,
279                                                   trans[i].length));
280                 cleanTranslation(tstr);
281                 trans_map_[ostr] = tstr;
282                 //lyxerr << ostr << " ==> " << tstr << endl;
283         }
284
285         return true;
286 }
287
288 docstring const Messages::get(string const & m) const
289 {
290         if (m.empty())
291                 return docstring();
292
293         TranslationMap::const_iterator it = trans_map_.find(m);
294         if (it != trans_map_.end())
295                 return it->second;
296         else {
297                 docstring res = from_utf8(m);
298                 cleanTranslation(res);
299                 return res;
300         }
301 }
302
303 } // namespace lyx
304
305 #else // ENABLE_NLS
306 // This is the dummy variant.
307
308 namespace lyx {
309
310 Messages::Messages(string const & /* l */) {}
311
312 docstring const Messages::get(string const & m) const
313 {
314         docstring trans = from_ascii(m);
315         cleanTranslation(trans);
316         return trans;
317 }
318
319 std::string Messages::language() const
320     {
321         return string();
322     }
323
324 bool Messages::available(string const & /* c */)
325 {
326         return false;
327 }
328
329 } // namespace lyx
330
331 #endif