]>
Commit | Line | Data |
---|---|---|
ea144923 VS |
1 | ///////////////////////////////////////////////////////////////////////////// |
2 | // Name: src/common/translation.cpp | |
3 | // Purpose: Internationalization and localisation for wxWidgets | |
4 | // Author: Vadim Zeitlin, Vaclav Slavik, | |
5 | // Michael N. Filippov <michael@idisys.iae.nsk.su> | |
6 | // (2003/09/30 - PluralForms support) | |
7 | // Created: 2010-04-23 | |
8 | // RCS-ID: $Id$ | |
9 | // Copyright: (c) 1998 Vadim Zeitlin <zeitlin@dptmaths.ens-cachan.fr> | |
10 | // Licence: wxWindows licence | |
11 | ///////////////////////////////////////////////////////////////////////////// | |
12 | ||
13 | // ============================================================================ | |
14 | // declaration | |
15 | // ============================================================================ | |
16 | ||
17 | // ---------------------------------------------------------------------------- | |
18 | // headers | |
19 | // ---------------------------------------------------------------------------- | |
20 | ||
21 | // For compilers that support precompilation, includes "wx.h". | |
22 | #include "wx/wxprec.h" | |
23 | ||
24 | #ifdef __BORLANDC__ | |
25 | #pragma hdrstop | |
26 | #endif | |
27 | ||
28 | #if wxUSE_INTL | |
29 | ||
30 | #ifndef WX_PRECOMP | |
31 | #include "wx/dynarray.h" | |
32 | #include "wx/string.h" | |
33 | #include "wx/intl.h" | |
34 | #include "wx/log.h" | |
35 | #include "wx/utils.h" | |
36 | #include "wx/hashmap.h" | |
37 | #include "wx/module.h" | |
38 | #endif // WX_PRECOMP | |
39 | ||
40 | // standard headers | |
41 | #include <ctype.h> | |
42 | #include <stdlib.h> | |
43 | ||
44 | #include "wx/file.h" | |
45 | #include "wx/filename.h" | |
46 | #include "wx/tokenzr.h" | |
47 | #include "wx/fontmap.h" | |
48 | #include "wx/scopedptr.h" | |
49 | #include "wx/stdpaths.h" | |
50 | #include "wx/hashset.h" | |
51 | ||
52 | // ---------------------------------------------------------------------------- | |
53 | // simple types | |
54 | // ---------------------------------------------------------------------------- | |
55 | ||
56 | typedef wxUint32 size_t32; | |
57 | ||
58 | // ---------------------------------------------------------------------------- | |
59 | // constants | |
60 | // ---------------------------------------------------------------------------- | |
61 | ||
62 | // magic number identifying the .mo format file | |
63 | const size_t32 MSGCATALOG_MAGIC = 0x950412de; | |
64 | const size_t32 MSGCATALOG_MAGIC_SW = 0xde120495; | |
65 | ||
66 | #define TRACE_I18N wxS("i18n") | |
67 | ||
68 | // the constants describing the format of ll_CC locale string | |
69 | static const size_t LEN_LANG = 2; | |
70 | ||
71 | // ---------------------------------------------------------------------------- | |
72 | // global functions | |
73 | // ---------------------------------------------------------------------------- | |
74 | ||
75 | namespace | |
76 | { | |
77 | ||
78 | // get just the language part | |
79 | inline wxString ExtractLang(const wxString& langFull) | |
80 | { | |
81 | return langFull.Left(LEN_LANG); | |
82 | } | |
83 | ||
84 | } // anonymous namespace | |
85 | ||
86 | ||
87 | // ============================================================================ | |
88 | // implementation | |
89 | // ============================================================================ | |
90 | ||
91 | // ---------------------------------------------------------------------------- | |
92 | // Plural forms parser | |
93 | // ---------------------------------------------------------------------------- | |
94 | ||
95 | /* | |
96 | Simplified Grammar | |
97 | ||
98 | Expression: | |
99 | LogicalOrExpression '?' Expression ':' Expression | |
100 | LogicalOrExpression | |
101 | ||
102 | LogicalOrExpression: | |
103 | LogicalAndExpression "||" LogicalOrExpression // to (a || b) || c | |
104 | LogicalAndExpression | |
105 | ||
106 | LogicalAndExpression: | |
107 | EqualityExpression "&&" LogicalAndExpression // to (a && b) && c | |
108 | EqualityExpression | |
109 | ||
110 | EqualityExpression: | |
111 | RelationalExpression "==" RelationalExperession | |
112 | RelationalExpression "!=" RelationalExperession | |
113 | RelationalExpression | |
114 | ||
115 | RelationalExpression: | |
116 | MultiplicativeExpression '>' MultiplicativeExpression | |
117 | MultiplicativeExpression '<' MultiplicativeExpression | |
118 | MultiplicativeExpression ">=" MultiplicativeExpression | |
119 | MultiplicativeExpression "<=" MultiplicativeExpression | |
120 | MultiplicativeExpression | |
121 | ||
122 | MultiplicativeExpression: | |
123 | PmExpression '%' PmExpression | |
124 | PmExpression | |
125 | ||
126 | PmExpression: | |
127 | N | |
128 | Number | |
129 | '(' Expression ')' | |
130 | */ | |
131 | ||
132 | class wxPluralFormsToken | |
133 | { | |
134 | public: | |
135 | enum Type | |
136 | { | |
137 | T_ERROR, T_EOF, T_NUMBER, T_N, T_PLURAL, T_NPLURALS, T_EQUAL, T_ASSIGN, | |
138 | T_GREATER, T_GREATER_OR_EQUAL, T_LESS, T_LESS_OR_EQUAL, | |
139 | T_REMINDER, T_NOT_EQUAL, | |
140 | T_LOGICAL_AND, T_LOGICAL_OR, T_QUESTION, T_COLON, T_SEMICOLON, | |
141 | T_LEFT_BRACKET, T_RIGHT_BRACKET | |
142 | }; | |
143 | Type type() const { return m_type; } | |
144 | void setType(Type type) { m_type = type; } | |
145 | // for T_NUMBER only | |
146 | typedef int Number; | |
147 | Number number() const { return m_number; } | |
148 | void setNumber(Number num) { m_number = num; } | |
149 | private: | |
150 | Type m_type; | |
151 | Number m_number; | |
152 | }; | |
153 | ||
154 | ||
155 | class wxPluralFormsScanner | |
156 | { | |
157 | public: | |
158 | wxPluralFormsScanner(const char* s); | |
159 | const wxPluralFormsToken& token() const { return m_token; } | |
160 | bool nextToken(); // returns false if error | |
161 | private: | |
162 | const char* m_s; | |
163 | wxPluralFormsToken m_token; | |
164 | }; | |
165 | ||
166 | wxPluralFormsScanner::wxPluralFormsScanner(const char* s) : m_s(s) | |
167 | { | |
168 | nextToken(); | |
169 | } | |
170 | ||
171 | bool wxPluralFormsScanner::nextToken() | |
172 | { | |
173 | wxPluralFormsToken::Type type = wxPluralFormsToken::T_ERROR; | |
174 | while (isspace((unsigned char) *m_s)) | |
175 | { | |
176 | ++m_s; | |
177 | } | |
178 | if (*m_s == 0) | |
179 | { | |
180 | type = wxPluralFormsToken::T_EOF; | |
181 | } | |
182 | else if (isdigit((unsigned char) *m_s)) | |
183 | { | |
184 | wxPluralFormsToken::Number number = *m_s++ - '0'; | |
185 | while (isdigit((unsigned char) *m_s)) | |
186 | { | |
187 | number = number * 10 + (*m_s++ - '0'); | |
188 | } | |
189 | m_token.setNumber(number); | |
190 | type = wxPluralFormsToken::T_NUMBER; | |
191 | } | |
192 | else if (isalpha((unsigned char) *m_s)) | |
193 | { | |
194 | const char* begin = m_s++; | |
195 | while (isalnum((unsigned char) *m_s)) | |
196 | { | |
197 | ++m_s; | |
198 | } | |
199 | size_t size = m_s - begin; | |
200 | if (size == 1 && memcmp(begin, "n", size) == 0) | |
201 | { | |
202 | type = wxPluralFormsToken::T_N; | |
203 | } | |
204 | else if (size == 6 && memcmp(begin, "plural", size) == 0) | |
205 | { | |
206 | type = wxPluralFormsToken::T_PLURAL; | |
207 | } | |
208 | else if (size == 8 && memcmp(begin, "nplurals", size) == 0) | |
209 | { | |
210 | type = wxPluralFormsToken::T_NPLURALS; | |
211 | } | |
212 | } | |
213 | else if (*m_s == '=') | |
214 | { | |
215 | ++m_s; | |
216 | if (*m_s == '=') | |
217 | { | |
218 | ++m_s; | |
219 | type = wxPluralFormsToken::T_EQUAL; | |
220 | } | |
221 | else | |
222 | { | |
223 | type = wxPluralFormsToken::T_ASSIGN; | |
224 | } | |
225 | } | |
226 | else if (*m_s == '>') | |
227 | { | |
228 | ++m_s; | |
229 | if (*m_s == '=') | |
230 | { | |
231 | ++m_s; | |
232 | type = wxPluralFormsToken::T_GREATER_OR_EQUAL; | |
233 | } | |
234 | else | |
235 | { | |
236 | type = wxPluralFormsToken::T_GREATER; | |
237 | } | |
238 | } | |
239 | else if (*m_s == '<') | |
240 | { | |
241 | ++m_s; | |
242 | if (*m_s == '=') | |
243 | { | |
244 | ++m_s; | |
245 | type = wxPluralFormsToken::T_LESS_OR_EQUAL; | |
246 | } | |
247 | else | |
248 | { | |
249 | type = wxPluralFormsToken::T_LESS; | |
250 | } | |
251 | } | |
252 | else if (*m_s == '%') | |
253 | { | |
254 | ++m_s; | |
255 | type = wxPluralFormsToken::T_REMINDER; | |
256 | } | |
257 | else if (*m_s == '!' && m_s[1] == '=') | |
258 | { | |
259 | m_s += 2; | |
260 | type = wxPluralFormsToken::T_NOT_EQUAL; | |
261 | } | |
262 | else if (*m_s == '&' && m_s[1] == '&') | |
263 | { | |
264 | m_s += 2; | |
265 | type = wxPluralFormsToken::T_LOGICAL_AND; | |
266 | } | |
267 | else if (*m_s == '|' && m_s[1] == '|') | |
268 | { | |
269 | m_s += 2; | |
270 | type = wxPluralFormsToken::T_LOGICAL_OR; | |
271 | } | |
272 | else if (*m_s == '?') | |
273 | { | |
274 | ++m_s; | |
275 | type = wxPluralFormsToken::T_QUESTION; | |
276 | } | |
277 | else if (*m_s == ':') | |
278 | { | |
279 | ++m_s; | |
280 | type = wxPluralFormsToken::T_COLON; | |
281 | } else if (*m_s == ';') { | |
282 | ++m_s; | |
283 | type = wxPluralFormsToken::T_SEMICOLON; | |
284 | } | |
285 | else if (*m_s == '(') | |
286 | { | |
287 | ++m_s; | |
288 | type = wxPluralFormsToken::T_LEFT_BRACKET; | |
289 | } | |
290 | else if (*m_s == ')') | |
291 | { | |
292 | ++m_s; | |
293 | type = wxPluralFormsToken::T_RIGHT_BRACKET; | |
294 | } | |
295 | m_token.setType(type); | |
296 | return type != wxPluralFormsToken::T_ERROR; | |
297 | } | |
298 | ||
299 | class wxPluralFormsNode; | |
300 | ||
301 | // NB: Can't use wxDEFINE_SCOPED_PTR_TYPE because wxPluralFormsNode is not | |
302 | // fully defined yet: | |
303 | class wxPluralFormsNodePtr | |
304 | { | |
305 | public: | |
306 | wxPluralFormsNodePtr(wxPluralFormsNode *p = NULL) : m_p(p) {} | |
307 | ~wxPluralFormsNodePtr(); | |
308 | wxPluralFormsNode& operator*() const { return *m_p; } | |
309 | wxPluralFormsNode* operator->() const { return m_p; } | |
310 | wxPluralFormsNode* get() const { return m_p; } | |
311 | wxPluralFormsNode* release(); | |
312 | void reset(wxPluralFormsNode *p); | |
313 | ||
314 | private: | |
315 | wxPluralFormsNode *m_p; | |
316 | }; | |
317 | ||
318 | class wxPluralFormsNode | |
319 | { | |
320 | public: | |
321 | wxPluralFormsNode(const wxPluralFormsToken& token) : m_token(token) {} | |
322 | const wxPluralFormsToken& token() const { return m_token; } | |
323 | const wxPluralFormsNode* node(size_t i) const | |
324 | { return m_nodes[i].get(); } | |
325 | void setNode(size_t i, wxPluralFormsNode* n); | |
326 | wxPluralFormsNode* releaseNode(size_t i); | |
327 | wxPluralFormsToken::Number evaluate(wxPluralFormsToken::Number n) const; | |
328 | ||
329 | private: | |
330 | wxPluralFormsToken m_token; | |
331 | wxPluralFormsNodePtr m_nodes[3]; | |
332 | }; | |
333 | ||
334 | wxPluralFormsNodePtr::~wxPluralFormsNodePtr() | |
335 | { | |
336 | delete m_p; | |
337 | } | |
338 | wxPluralFormsNode* wxPluralFormsNodePtr::release() | |
339 | { | |
340 | wxPluralFormsNode *p = m_p; | |
341 | m_p = NULL; | |
342 | return p; | |
343 | } | |
344 | void wxPluralFormsNodePtr::reset(wxPluralFormsNode *p) | |
345 | { | |
346 | if (p != m_p) | |
347 | { | |
348 | delete m_p; | |
349 | m_p = p; | |
350 | } | |
351 | } | |
352 | ||
353 | ||
354 | void wxPluralFormsNode::setNode(size_t i, wxPluralFormsNode* n) | |
355 | { | |
356 | m_nodes[i].reset(n); | |
357 | } | |
358 | ||
359 | wxPluralFormsNode* wxPluralFormsNode::releaseNode(size_t i) | |
360 | { | |
361 | return m_nodes[i].release(); | |
362 | } | |
363 | ||
364 | wxPluralFormsToken::Number | |
365 | wxPluralFormsNode::evaluate(wxPluralFormsToken::Number n) const | |
366 | { | |
367 | switch (token().type()) | |
368 | { | |
369 | // leaf | |
370 | case wxPluralFormsToken::T_NUMBER: | |
371 | return token().number(); | |
372 | case wxPluralFormsToken::T_N: | |
373 | return n; | |
374 | // 2 args | |
375 | case wxPluralFormsToken::T_EQUAL: | |
376 | return node(0)->evaluate(n) == node(1)->evaluate(n); | |
377 | case wxPluralFormsToken::T_NOT_EQUAL: | |
378 | return node(0)->evaluate(n) != node(1)->evaluate(n); | |
379 | case wxPluralFormsToken::T_GREATER: | |
380 | return node(0)->evaluate(n) > node(1)->evaluate(n); | |
381 | case wxPluralFormsToken::T_GREATER_OR_EQUAL: | |
382 | return node(0)->evaluate(n) >= node(1)->evaluate(n); | |
383 | case wxPluralFormsToken::T_LESS: | |
384 | return node(0)->evaluate(n) < node(1)->evaluate(n); | |
385 | case wxPluralFormsToken::T_LESS_OR_EQUAL: | |
386 | return node(0)->evaluate(n) <= node(1)->evaluate(n); | |
387 | case wxPluralFormsToken::T_REMINDER: | |
388 | { | |
389 | wxPluralFormsToken::Number number = node(1)->evaluate(n); | |
390 | if (number != 0) | |
391 | { | |
392 | return node(0)->evaluate(n) % number; | |
393 | } | |
394 | else | |
395 | { | |
396 | return 0; | |
397 | } | |
398 | } | |
399 | case wxPluralFormsToken::T_LOGICAL_AND: | |
400 | return node(0)->evaluate(n) && node(1)->evaluate(n); | |
401 | case wxPluralFormsToken::T_LOGICAL_OR: | |
402 | return node(0)->evaluate(n) || node(1)->evaluate(n); | |
403 | // 3 args | |
404 | case wxPluralFormsToken::T_QUESTION: | |
405 | return node(0)->evaluate(n) | |
406 | ? node(1)->evaluate(n) | |
407 | : node(2)->evaluate(n); | |
408 | default: | |
409 | return 0; | |
410 | } | |
411 | } | |
412 | ||
413 | ||
414 | class wxPluralFormsCalculator | |
415 | { | |
416 | public: | |
417 | wxPluralFormsCalculator() : m_nplurals(0), m_plural(0) {} | |
418 | ||
419 | // input: number, returns msgstr index | |
420 | int evaluate(int n) const; | |
421 | ||
422 | // input: text after "Plural-Forms:" (e.g. "nplurals=2; plural=(n != 1);"), | |
423 | // if s == 0, creates default handler | |
424 | // returns 0 if error | |
425 | static wxPluralFormsCalculator* make(const char* s = 0); | |
426 | ||
427 | ~wxPluralFormsCalculator() {} | |
428 | ||
429 | void init(wxPluralFormsToken::Number nplurals, wxPluralFormsNode* plural); | |
430 | ||
431 | private: | |
432 | wxPluralFormsToken::Number m_nplurals; | |
433 | wxPluralFormsNodePtr m_plural; | |
434 | }; | |
435 | ||
436 | wxDEFINE_SCOPED_PTR_TYPE(wxPluralFormsCalculator) | |
437 | ||
438 | void wxPluralFormsCalculator::init(wxPluralFormsToken::Number nplurals, | |
439 | wxPluralFormsNode* plural) | |
440 | { | |
441 | m_nplurals = nplurals; | |
442 | m_plural.reset(plural); | |
443 | } | |
444 | ||
445 | int wxPluralFormsCalculator::evaluate(int n) const | |
446 | { | |
447 | if (m_plural.get() == 0) | |
448 | { | |
449 | return 0; | |
450 | } | |
451 | wxPluralFormsToken::Number number = m_plural->evaluate(n); | |
452 | if (number < 0 || number > m_nplurals) | |
453 | { | |
454 | return 0; | |
455 | } | |
456 | return number; | |
457 | } | |
458 | ||
459 | ||
460 | class wxPluralFormsParser | |
461 | { | |
462 | public: | |
463 | wxPluralFormsParser(wxPluralFormsScanner& scanner) : m_scanner(scanner) {} | |
464 | bool parse(wxPluralFormsCalculator& rCalculator); | |
465 | ||
466 | private: | |
467 | wxPluralFormsNode* parsePlural(); | |
468 | // stops at T_SEMICOLON, returns 0 if error | |
469 | wxPluralFormsScanner& m_scanner; | |
470 | const wxPluralFormsToken& token() const; | |
471 | bool nextToken(); | |
472 | ||
473 | wxPluralFormsNode* expression(); | |
474 | wxPluralFormsNode* logicalOrExpression(); | |
475 | wxPluralFormsNode* logicalAndExpression(); | |
476 | wxPluralFormsNode* equalityExpression(); | |
477 | wxPluralFormsNode* multiplicativeExpression(); | |
478 | wxPluralFormsNode* relationalExpression(); | |
479 | wxPluralFormsNode* pmExpression(); | |
480 | }; | |
481 | ||
482 | bool wxPluralFormsParser::parse(wxPluralFormsCalculator& rCalculator) | |
483 | { | |
484 | if (token().type() != wxPluralFormsToken::T_NPLURALS) | |
485 | return false; | |
486 | if (!nextToken()) | |
487 | return false; | |
488 | if (token().type() != wxPluralFormsToken::T_ASSIGN) | |
489 | return false; | |
490 | if (!nextToken()) | |
491 | return false; | |
492 | if (token().type() != wxPluralFormsToken::T_NUMBER) | |
493 | return false; | |
494 | wxPluralFormsToken::Number nplurals = token().number(); | |
495 | if (!nextToken()) | |
496 | return false; | |
497 | if (token().type() != wxPluralFormsToken::T_SEMICOLON) | |
498 | return false; | |
499 | if (!nextToken()) | |
500 | return false; | |
501 | if (token().type() != wxPluralFormsToken::T_PLURAL) | |
502 | return false; | |
503 | if (!nextToken()) | |
504 | return false; | |
505 | if (token().type() != wxPluralFormsToken::T_ASSIGN) | |
506 | return false; | |
507 | if (!nextToken()) | |
508 | return false; | |
509 | wxPluralFormsNode* plural = parsePlural(); | |
510 | if (plural == 0) | |
511 | return false; | |
512 | if (token().type() != wxPluralFormsToken::T_SEMICOLON) | |
513 | return false; | |
514 | if (!nextToken()) | |
515 | return false; | |
516 | if (token().type() != wxPluralFormsToken::T_EOF) | |
517 | return false; | |
518 | rCalculator.init(nplurals, plural); | |
519 | return true; | |
520 | } | |
521 | ||
522 | wxPluralFormsNode* wxPluralFormsParser::parsePlural() | |
523 | { | |
524 | wxPluralFormsNode* p = expression(); | |
525 | if (p == NULL) | |
526 | { | |
527 | return NULL; | |
528 | } | |
529 | wxPluralFormsNodePtr n(p); | |
530 | if (token().type() != wxPluralFormsToken::T_SEMICOLON) | |
531 | { | |
532 | return NULL; | |
533 | } | |
534 | return n.release(); | |
535 | } | |
536 | ||
537 | const wxPluralFormsToken& wxPluralFormsParser::token() const | |
538 | { | |
539 | return m_scanner.token(); | |
540 | } | |
541 | ||
542 | bool wxPluralFormsParser::nextToken() | |
543 | { | |
544 | if (!m_scanner.nextToken()) | |
545 | return false; | |
546 | return true; | |
547 | } | |
548 | ||
549 | wxPluralFormsNode* wxPluralFormsParser::expression() | |
550 | { | |
551 | wxPluralFormsNode* p = logicalOrExpression(); | |
552 | if (p == NULL) | |
553 | return NULL; | |
554 | wxPluralFormsNodePtr n(p); | |
555 | if (token().type() == wxPluralFormsToken::T_QUESTION) | |
556 | { | |
557 | wxPluralFormsNodePtr qn(new wxPluralFormsNode(token())); | |
558 | if (!nextToken()) | |
559 | { | |
560 | return 0; | |
561 | } | |
562 | p = expression(); | |
563 | if (p == 0) | |
564 | { | |
565 | return 0; | |
566 | } | |
567 | qn->setNode(1, p); | |
568 | if (token().type() != wxPluralFormsToken::T_COLON) | |
569 | { | |
570 | return 0; | |
571 | } | |
572 | if (!nextToken()) | |
573 | { | |
574 | return 0; | |
575 | } | |
576 | p = expression(); | |
577 | if (p == 0) | |
578 | { | |
579 | return 0; | |
580 | } | |
581 | qn->setNode(2, p); | |
582 | qn->setNode(0, n.release()); | |
583 | return qn.release(); | |
584 | } | |
585 | return n.release(); | |
586 | } | |
587 | ||
588 | wxPluralFormsNode*wxPluralFormsParser::logicalOrExpression() | |
589 | { | |
590 | wxPluralFormsNode* p = logicalAndExpression(); | |
591 | if (p == NULL) | |
592 | return NULL; | |
593 | wxPluralFormsNodePtr ln(p); | |
594 | if (token().type() == wxPluralFormsToken::T_LOGICAL_OR) | |
595 | { | |
596 | wxPluralFormsNodePtr un(new wxPluralFormsNode(token())); | |
597 | if (!nextToken()) | |
598 | { | |
599 | return 0; | |
600 | } | |
601 | p = logicalOrExpression(); | |
602 | if (p == 0) | |
603 | { | |
604 | return 0; | |
605 | } | |
606 | wxPluralFormsNodePtr rn(p); // right | |
607 | if (rn->token().type() == wxPluralFormsToken::T_LOGICAL_OR) | |
608 | { | |
609 | // see logicalAndExpression comment | |
610 | un->setNode(0, ln.release()); | |
611 | un->setNode(1, rn->releaseNode(0)); | |
612 | rn->setNode(0, un.release()); | |
613 | return rn.release(); | |
614 | } | |
615 | ||
616 | ||
617 | un->setNode(0, ln.release()); | |
618 | un->setNode(1, rn.release()); | |
619 | return un.release(); | |
620 | } | |
621 | return ln.release(); | |
622 | } | |
623 | ||
624 | wxPluralFormsNode* wxPluralFormsParser::logicalAndExpression() | |
625 | { | |
626 | wxPluralFormsNode* p = equalityExpression(); | |
627 | if (p == NULL) | |
628 | return NULL; | |
629 | wxPluralFormsNodePtr ln(p); // left | |
630 | if (token().type() == wxPluralFormsToken::T_LOGICAL_AND) | |
631 | { | |
632 | wxPluralFormsNodePtr un(new wxPluralFormsNode(token())); // up | |
633 | if (!nextToken()) | |
634 | { | |
635 | return NULL; | |
636 | } | |
637 | p = logicalAndExpression(); | |
638 | if (p == 0) | |
639 | { | |
640 | return NULL; | |
641 | } | |
642 | wxPluralFormsNodePtr rn(p); // right | |
643 | if (rn->token().type() == wxPluralFormsToken::T_LOGICAL_AND) | |
644 | { | |
645 | // transform 1 && (2 && 3) -> (1 && 2) && 3 | |
646 | // u r | |
647 | // l r -> u 3 | |
648 | // 2 3 l 2 | |
649 | un->setNode(0, ln.release()); | |
650 | un->setNode(1, rn->releaseNode(0)); | |
651 | rn->setNode(0, un.release()); | |
652 | return rn.release(); | |
653 | } | |
654 | ||
655 | un->setNode(0, ln.release()); | |
656 | un->setNode(1, rn.release()); | |
657 | return un.release(); | |
658 | } | |
659 | return ln.release(); | |
660 | } | |
661 | ||
662 | wxPluralFormsNode* wxPluralFormsParser::equalityExpression() | |
663 | { | |
664 | wxPluralFormsNode* p = relationalExpression(); | |
665 | if (p == NULL) | |
666 | return NULL; | |
667 | wxPluralFormsNodePtr n(p); | |
668 | if (token().type() == wxPluralFormsToken::T_EQUAL | |
669 | || token().type() == wxPluralFormsToken::T_NOT_EQUAL) | |
670 | { | |
671 | wxPluralFormsNodePtr qn(new wxPluralFormsNode(token())); | |
672 | if (!nextToken()) | |
673 | { | |
674 | return NULL; | |
675 | } | |
676 | p = relationalExpression(); | |
677 | if (p == NULL) | |
678 | { | |
679 | return NULL; | |
680 | } | |
681 | qn->setNode(1, p); | |
682 | qn->setNode(0, n.release()); | |
683 | return qn.release(); | |
684 | } | |
685 | return n.release(); | |
686 | } | |
687 | ||
688 | wxPluralFormsNode* wxPluralFormsParser::relationalExpression() | |
689 | { | |
690 | wxPluralFormsNode* p = multiplicativeExpression(); | |
691 | if (p == NULL) | |
692 | return NULL; | |
693 | wxPluralFormsNodePtr n(p); | |
694 | if (token().type() == wxPluralFormsToken::T_GREATER | |
695 | || token().type() == wxPluralFormsToken::T_LESS | |
696 | || token().type() == wxPluralFormsToken::T_GREATER_OR_EQUAL | |
697 | || token().type() == wxPluralFormsToken::T_LESS_OR_EQUAL) | |
698 | { | |
699 | wxPluralFormsNodePtr qn(new wxPluralFormsNode(token())); | |
700 | if (!nextToken()) | |
701 | { | |
702 | return NULL; | |
703 | } | |
704 | p = multiplicativeExpression(); | |
705 | if (p == NULL) | |
706 | { | |
707 | return NULL; | |
708 | } | |
709 | qn->setNode(1, p); | |
710 | qn->setNode(0, n.release()); | |
711 | return qn.release(); | |
712 | } | |
713 | return n.release(); | |
714 | } | |
715 | ||
716 | wxPluralFormsNode* wxPluralFormsParser::multiplicativeExpression() | |
717 | { | |
718 | wxPluralFormsNode* p = pmExpression(); | |
719 | if (p == NULL) | |
720 | return NULL; | |
721 | wxPluralFormsNodePtr n(p); | |
722 | if (token().type() == wxPluralFormsToken::T_REMINDER) | |
723 | { | |
724 | wxPluralFormsNodePtr qn(new wxPluralFormsNode(token())); | |
725 | if (!nextToken()) | |
726 | { | |
727 | return NULL; | |
728 | } | |
729 | p = pmExpression(); | |
730 | if (p == NULL) | |
731 | { | |
732 | return NULL; | |
733 | } | |
734 | qn->setNode(1, p); | |
735 | qn->setNode(0, n.release()); | |
736 | return qn.release(); | |
737 | } | |
738 | return n.release(); | |
739 | } | |
740 | ||
741 | wxPluralFormsNode* wxPluralFormsParser::pmExpression() | |
742 | { | |
743 | wxPluralFormsNodePtr n; | |
744 | if (token().type() == wxPluralFormsToken::T_N | |
745 | || token().type() == wxPluralFormsToken::T_NUMBER) | |
746 | { | |
747 | n.reset(new wxPluralFormsNode(token())); | |
748 | if (!nextToken()) | |
749 | { | |
750 | return NULL; | |
751 | } | |
752 | } | |
753 | else if (token().type() == wxPluralFormsToken::T_LEFT_BRACKET) { | |
754 | if (!nextToken()) | |
755 | { | |
756 | return NULL; | |
757 | } | |
758 | wxPluralFormsNode* p = expression(); | |
759 | if (p == NULL) | |
760 | { | |
761 | return NULL; | |
762 | } | |
763 | n.reset(p); | |
764 | if (token().type() != wxPluralFormsToken::T_RIGHT_BRACKET) | |
765 | { | |
766 | return NULL; | |
767 | } | |
768 | if (!nextToken()) | |
769 | { | |
770 | return NULL; | |
771 | } | |
772 | } | |
773 | else | |
774 | { | |
775 | return NULL; | |
776 | } | |
777 | return n.release(); | |
778 | } | |
779 | ||
780 | wxPluralFormsCalculator* wxPluralFormsCalculator::make(const char* s) | |
781 | { | |
782 | wxPluralFormsCalculatorPtr calculator(new wxPluralFormsCalculator); | |
783 | if (s != NULL) | |
784 | { | |
785 | wxPluralFormsScanner scanner(s); | |
786 | wxPluralFormsParser p(scanner); | |
787 | if (!p.parse(*calculator)) | |
788 | { | |
789 | return NULL; | |
790 | } | |
791 | } | |
792 | return calculator.release(); | |
793 | } | |
794 | ||
795 | ||
796 | ||
797 | ||
798 | // ---------------------------------------------------------------------------- | |
799 | // wxMsgCatalogFile corresponds to one disk-file message catalog. | |
800 | // | |
801 | // This is a "low-level" class and is used only by wxMsgCatalog | |
802 | // NOTE: for the documentation of the binary catalog (.MO) files refer to | |
803 | // the GNU gettext manual: | |
804 | // http://www.gnu.org/software/autoconf/manual/gettext/MO-Files.html | |
805 | // ---------------------------------------------------------------------------- | |
806 | ||
807 | WX_DECLARE_EXPORTED_STRING_HASH_MAP(wxString, wxMessagesHash); | |
808 | ||
809 | class wxMsgCatalogFile | |
810 | { | |
811 | public: | |
812 | // ctor & dtor | |
813 | wxMsgCatalogFile(); | |
814 | ~wxMsgCatalogFile(); | |
815 | ||
816 | // load the catalog from disk | |
817 | bool Load(const wxString& filename, | |
818 | wxPluralFormsCalculatorPtr& rPluralFormsCalculator); | |
819 | ||
820 | // fills the hash with string-translation pairs | |
821 | bool FillHash(wxMessagesHash& hash, const wxString& msgIdCharset) const; | |
822 | ||
823 | // return the charset of the strings in this catalog or empty string if | |
824 | // none/unknown | |
825 | wxString GetCharset() const { return m_charset; } | |
826 | ||
827 | private: | |
828 | // this implementation is binary compatible with GNU gettext() version 0.10 | |
829 | ||
830 | // an entry in the string table | |
831 | struct wxMsgTableEntry | |
832 | { | |
833 | size_t32 nLen; // length of the string | |
834 | size_t32 ofsString; // pointer to the string | |
835 | }; | |
836 | ||
837 | // header of a .mo file | |
838 | struct wxMsgCatalogHeader | |
839 | { | |
840 | size_t32 magic, // offset +00: magic id | |
841 | revision, // +04: revision | |
842 | numStrings; // +08: number of strings in the file | |
843 | size_t32 ofsOrigTable, // +0C: start of original string table | |
844 | ofsTransTable; // +10: start of translated string table | |
845 | size_t32 nHashSize, // +14: hash table size | |
846 | ofsHashTable; // +18: offset of hash table start | |
847 | }; | |
848 | ||
849 | // all data is stored here | |
850 | wxMemoryBuffer m_data; | |
851 | ||
852 | // data description | |
853 | size_t32 m_numStrings; // number of strings in this domain | |
854 | wxMsgTableEntry *m_pOrigTable, // pointer to original strings | |
855 | *m_pTransTable; // translated | |
856 | ||
857 | wxString m_charset; // from the message catalog header | |
858 | ||
859 | ||
860 | // swap the 2 halves of 32 bit integer if needed | |
861 | size_t32 Swap(size_t32 ui) const | |
862 | { | |
863 | return m_bSwapped ? (ui << 24) | ((ui & 0xff00) << 8) | | |
864 | ((ui >> 8) & 0xff00) | (ui >> 24) | |
865 | : ui; | |
866 | } | |
867 | ||
868 | // just return the pointer to the start of the data as "char *" to | |
869 | // facilitate doing pointer arithmetic with it | |
870 | char *StringData() const | |
871 | { | |
872 | return static_cast<char *>(m_data.GetData()); | |
873 | } | |
874 | ||
875 | const char *StringAtOfs(wxMsgTableEntry *pTable, size_t32 n) const | |
876 | { | |
877 | const wxMsgTableEntry * const ent = pTable + n; | |
878 | ||
879 | // this check could fail for a corrupt message catalog | |
880 | size_t32 ofsString = Swap(ent->ofsString); | |
881 | if ( ofsString + Swap(ent->nLen) > m_data.GetDataLen()) | |
882 | { | |
883 | return NULL; | |
884 | } | |
885 | ||
886 | return StringData() + ofsString; | |
887 | } | |
888 | ||
889 | bool m_bSwapped; // wrong endianness? | |
890 | ||
891 | wxDECLARE_NO_COPY_CLASS(wxMsgCatalogFile); | |
892 | }; | |
893 | ||
894 | ||
895 | // ---------------------------------------------------------------------------- | |
896 | // wxMsgCatalog corresponds to one loaded message catalog. | |
897 | // | |
898 | // This is a "low-level" class and is used only by wxLocale (that's why | |
899 | // it's designed to be stored in a linked list) | |
900 | // ---------------------------------------------------------------------------- | |
901 | ||
902 | class wxMsgCatalog | |
903 | { | |
904 | public: | |
905 | #if !wxUSE_UNICODE | |
906 | wxMsgCatalog() { m_conv = NULL; } | |
907 | ~wxMsgCatalog(); | |
908 | #endif | |
909 | ||
910 | // load the catalog from disk | |
911 | bool Load(const wxString& filename, | |
912 | const wxString& domain, | |
913 | const wxString& msgIdCharset); | |
914 | ||
915 | // get name of the catalog | |
916 | wxString GetDomain() const { return m_domain; } | |
917 | ||
918 | // get the translated string: returns NULL if not found | |
919 | const wxString *GetString(const wxString& sz, size_t n = size_t(-1)) const; | |
920 | ||
921 | // public variable pointing to the next element in a linked list (or NULL) | |
922 | wxMsgCatalog *m_pNext; | |
923 | ||
924 | private: | |
925 | wxMessagesHash m_messages; // all messages in the catalog | |
926 | wxString m_domain; // name of the domain | |
927 | ||
928 | #if !wxUSE_UNICODE | |
929 | // the conversion corresponding to this catalog charset if we installed it | |
930 | // as the global one | |
931 | wxCSConv *m_conv; | |
932 | #endif | |
933 | ||
934 | wxPluralFormsCalculatorPtr m_pluralFormsCalculator; | |
935 | }; | |
936 | ||
937 | // ---------------------------------------------------------------------------- | |
938 | // wxMsgCatalogFile clas | |
939 | // ---------------------------------------------------------------------------- | |
940 | ||
941 | wxMsgCatalogFile::wxMsgCatalogFile() | |
942 | { | |
943 | } | |
944 | ||
945 | wxMsgCatalogFile::~wxMsgCatalogFile() | |
946 | { | |
947 | } | |
948 | ||
949 | // open disk file and read in it's contents | |
950 | bool wxMsgCatalogFile::Load(const wxString& filename, | |
951 | wxPluralFormsCalculatorPtr& rPluralFormsCalculator) | |
952 | { | |
953 | wxFile fileMsg(filename); | |
954 | if ( !fileMsg.IsOpened() ) | |
955 | return false; | |
956 | ||
957 | // get the file size (assume it is less than 4Gb...) | |
958 | wxFileOffset lenFile = fileMsg.Length(); | |
959 | if ( lenFile == wxInvalidOffset ) | |
960 | return false; | |
961 | ||
962 | size_t nSize = wx_truncate_cast(size_t, lenFile); | |
963 | wxASSERT_MSG( nSize == lenFile + size_t(0), wxS("message catalog bigger than 4GB?") ); | |
964 | ||
965 | // read the whole file in memory | |
966 | if ( fileMsg.Read(m_data.GetWriteBuf(nSize), nSize) != lenFile ) | |
967 | return false; | |
968 | ||
969 | m_data.UngetWriteBuf(nSize); | |
970 | ||
971 | ||
972 | // examine header | |
973 | bool bValid = m_data.GetDataLen() > sizeof(wxMsgCatalogHeader); | |
974 | ||
975 | const wxMsgCatalogHeader *pHeader = (wxMsgCatalogHeader *)m_data.GetData(); | |
976 | if ( bValid ) { | |
977 | // we'll have to swap all the integers if it's true | |
978 | m_bSwapped = pHeader->magic == MSGCATALOG_MAGIC_SW; | |
979 | ||
980 | // check the magic number | |
981 | bValid = m_bSwapped || pHeader->magic == MSGCATALOG_MAGIC; | |
982 | } | |
983 | ||
984 | if ( !bValid ) { | |
985 | // it's either too short or has incorrect magic number | |
986 | wxLogWarning(_("'%s' is not a valid message catalog."), filename.c_str()); | |
987 | ||
988 | return false; | |
989 | } | |
990 | ||
991 | // initialize | |
992 | m_numStrings = Swap(pHeader->numStrings); | |
993 | m_pOrigTable = (wxMsgTableEntry *)(StringData() + | |
994 | Swap(pHeader->ofsOrigTable)); | |
995 | m_pTransTable = (wxMsgTableEntry *)(StringData() + | |
996 | Swap(pHeader->ofsTransTable)); | |
997 | ||
998 | // now parse catalog's header and try to extract catalog charset and | |
999 | // plural forms formula from it: | |
1000 | ||
1001 | const char* headerData = StringAtOfs(m_pOrigTable, 0); | |
1002 | if ( headerData && headerData[0] == '\0' ) | |
1003 | { | |
1004 | // Extract the charset: | |
1005 | const char * const header = StringAtOfs(m_pTransTable, 0); | |
1006 | const char * | |
1007 | cset = strstr(header, "Content-Type: text/plain; charset="); | |
1008 | if ( cset ) | |
1009 | { | |
1010 | cset += 34; // strlen("Content-Type: text/plain; charset=") | |
1011 | ||
1012 | const char * const csetEnd = strchr(cset, '\n'); | |
1013 | if ( csetEnd ) | |
1014 | { | |
1015 | m_charset = wxString(cset, csetEnd - cset); | |
1016 | if ( m_charset == wxS("CHARSET") ) | |
1017 | { | |
1018 | // "CHARSET" is not valid charset, but lazy translator | |
1019 | m_charset.empty(); | |
1020 | } | |
1021 | } | |
1022 | } | |
1023 | // else: incorrectly filled Content-Type header | |
1024 | ||
1025 | // Extract plural forms: | |
1026 | const char * plurals = strstr(header, "Plural-Forms:"); | |
1027 | if ( plurals ) | |
1028 | { | |
1029 | plurals += 13; // strlen("Plural-Forms:") | |
1030 | const char * const pluralsEnd = strchr(plurals, '\n'); | |
1031 | if ( pluralsEnd ) | |
1032 | { | |
1033 | const size_t pluralsLen = pluralsEnd - plurals; | |
1034 | wxCharBuffer buf(pluralsLen); | |
1035 | strncpy(buf.data(), plurals, pluralsLen); | |
1036 | wxPluralFormsCalculator * const | |
1037 | pCalculator = wxPluralFormsCalculator::make(buf); | |
1038 | if ( pCalculator ) | |
1039 | { | |
1040 | rPluralFormsCalculator.reset(pCalculator); | |
1041 | } | |
1042 | else | |
1043 | { | |
1044 | wxLogVerbose(_("Failed to parse Plural-Forms: '%s'"), | |
1045 | buf.data()); | |
1046 | } | |
1047 | } | |
1048 | } | |
1049 | ||
1050 | if ( !rPluralFormsCalculator.get() ) | |
1051 | rPluralFormsCalculator.reset(wxPluralFormsCalculator::make()); | |
1052 | } | |
1053 | ||
1054 | // everything is fine | |
1055 | return true; | |
1056 | } | |
1057 | ||
1058 | bool wxMsgCatalogFile::FillHash(wxMessagesHash& hash, | |
1059 | const wxString& msgIdCharset) const | |
1060 | { | |
1061 | wxUnusedVar(msgIdCharset); // silence warning in Unicode build | |
1062 | ||
1063 | // conversion to use to convert catalog strings to the GUI encoding | |
1064 | wxMBConv *inputConv = NULL; | |
1065 | wxMBConv *inputConvPtr = NULL; // same as inputConv but safely deleteable | |
1066 | ||
1067 | if ( !m_charset.empty() ) | |
1068 | { | |
1069 | #if !wxUSE_UNICODE && wxUSE_FONTMAP | |
1070 | // determine if we need any conversion at all | |
1071 | wxFontEncoding encCat = wxFontMapperBase::GetEncodingFromName(m_charset); | |
1072 | if ( encCat != wxLocale::GetSystemEncoding() ) | |
1073 | #endif | |
1074 | { | |
1075 | inputConvPtr = | |
1076 | inputConv = new wxCSConv(m_charset); | |
1077 | } | |
1078 | } | |
1079 | else // no need or not possible to convert the encoding | |
1080 | { | |
1081 | #if wxUSE_UNICODE | |
1082 | // we must somehow convert the narrow strings in the message catalog to | |
1083 | // wide strings, so use the default conversion if we have no charset | |
1084 | inputConv = wxConvCurrent; | |
1085 | #endif | |
1086 | } | |
1087 | ||
1088 | #if !wxUSE_UNICODE | |
1089 | // conversion to apply to msgid strings before looking them up: we only | |
1090 | // need it if the msgids are neither in 7 bit ASCII nor in the same | |
1091 | // encoding as the catalog | |
1092 | wxCSConv *sourceConv = msgIdCharset.empty() || (msgIdCharset == m_charset) | |
1093 | ? NULL | |
1094 | : new wxCSConv(msgIdCharset); | |
1095 | #endif // !wxUSE_UNICODE | |
1096 | ||
1097 | for (size_t32 i = 0; i < m_numStrings; i++) | |
1098 | { | |
1099 | const char *data = StringAtOfs(m_pOrigTable, i); | |
1100 | if (!data) | |
1101 | return false; // may happen for invalid MO files | |
1102 | ||
1103 | wxString msgid; | |
1104 | #if wxUSE_UNICODE | |
1105 | msgid = wxString(data, *inputConv); | |
1106 | #else // ASCII | |
1107 | if ( inputConv && sourceConv ) | |
1108 | msgid = wxString(inputConv->cMB2WC(data), *sourceConv); | |
1109 | else | |
1110 | msgid = data; | |
1111 | #endif // wxUSE_UNICODE | |
1112 | ||
1113 | data = StringAtOfs(m_pTransTable, i); | |
1114 | if (!data) | |
1115 | return false; // may happen for invalid MO files | |
1116 | ||
1117 | size_t length = Swap(m_pTransTable[i].nLen); | |
1118 | size_t offset = 0; | |
1119 | size_t index = 0; | |
1120 | while (offset < length) | |
1121 | { | |
1122 | const char * const str = data + offset; | |
1123 | ||
1124 | wxString msgstr; | |
1125 | #if wxUSE_UNICODE | |
1126 | msgstr = wxString(str, *inputConv); | |
1127 | #else | |
1128 | if ( inputConv ) | |
1129 | msgstr = wxString(inputConv->cMB2WC(str), *wxConvUI); | |
1130 | else | |
1131 | msgstr = str; | |
1132 | #endif // wxUSE_UNICODE/!wxUSE_UNICODE | |
1133 | ||
1134 | if ( !msgstr.empty() ) | |
1135 | { | |
1136 | hash[index == 0 ? msgid : msgid + wxChar(index)] = msgstr; | |
1137 | } | |
1138 | ||
1139 | // skip this string | |
1140 | // IMPORTANT: accesses to the 'data' pointer are valid only for | |
1141 | // the first 'length+1' bytes (GNU specs says that the | |
1142 | // final NUL is not counted in length); using wxStrnlen() | |
1143 | // we make sure we don't access memory beyond the valid range | |
1144 | // (which otherwise may happen for invalid MO files): | |
1145 | offset += wxStrnlen(str, length - offset) + 1; | |
1146 | ++index; | |
1147 | } | |
1148 | } | |
1149 | ||
1150 | #if !wxUSE_UNICODE | |
1151 | delete sourceConv; | |
1152 | #endif | |
1153 | delete inputConvPtr; | |
1154 | ||
1155 | return true; | |
1156 | } | |
1157 | ||
1158 | ||
1159 | // ---------------------------------------------------------------------------- | |
1160 | // wxMsgCatalog class | |
1161 | // ---------------------------------------------------------------------------- | |
1162 | ||
1163 | #if !wxUSE_UNICODE | |
1164 | wxMsgCatalog::~wxMsgCatalog() | |
1165 | { | |
1166 | if ( m_conv ) | |
1167 | { | |
1168 | if ( wxConvUI == m_conv ) | |
1169 | { | |
1170 | // we only change wxConvUI if it points to wxConvLocal so we reset | |
1171 | // it back to it too | |
1172 | wxConvUI = &wxConvLocal; | |
1173 | } | |
1174 | ||
1175 | delete m_conv; | |
1176 | } | |
1177 | } | |
1178 | #endif // !wxUSE_UNICODE | |
1179 | ||
1180 | bool wxMsgCatalog::Load(const wxString& filename, | |
1181 | const wxString& domain, | |
1182 | const wxString& msgIdCharset) | |
1183 | { | |
1184 | wxMsgCatalogFile file; | |
1185 | ||
1186 | m_domain = domain; | |
1187 | ||
1188 | if ( !file.Load(filename, m_pluralFormsCalculator) ) | |
1189 | return false; | |
1190 | ||
1191 | if ( !file.FillHash(m_messages, msgIdCharset) ) | |
1192 | return false; | |
1193 | ||
1194 | return true; | |
1195 | } | |
1196 | ||
1197 | const wxString *wxMsgCatalog::GetString(const wxString& str, size_t n) const | |
1198 | { | |
1199 | int index = 0; | |
1200 | if (n != size_t(-1)) | |
1201 | { | |
1202 | index = m_pluralFormsCalculator->evaluate(n); | |
1203 | } | |
1204 | wxMessagesHash::const_iterator i; | |
1205 | if (index != 0) | |
1206 | { | |
1207 | i = m_messages.find(wxString(str) + wxChar(index)); // plural | |
1208 | } | |
1209 | else | |
1210 | { | |
1211 | i = m_messages.find(str); | |
1212 | } | |
1213 | ||
1214 | if ( i != m_messages.end() ) | |
1215 | { | |
1216 | return &i->second; | |
1217 | } | |
1218 | else | |
1219 | return NULL; | |
1220 | } | |
1221 | ||
1222 | ||
1223 | // ---------------------------------------------------------------------------- | |
1224 | // wxTranslations | |
1225 | // ---------------------------------------------------------------------------- | |
1226 | ||
1227 | namespace | |
1228 | { | |
1229 | ||
1230 | wxTranslations *gs_translations = NULL; | |
1231 | bool gs_translationsOwned = false; | |
1232 | ||
1233 | } // anonymous namespace | |
1234 | ||
1235 | ||
1236 | /*static*/ | |
1237 | wxTranslations *wxTranslations::Get() | |
1238 | { | |
1239 | return gs_translations; | |
1240 | } | |
1241 | ||
1242 | /*static*/ | |
1243 | void wxTranslations::Set(wxTranslations *t) | |
1244 | { | |
1245 | if ( gs_translationsOwned ) | |
1246 | delete gs_translations; | |
1247 | gs_translations = t; | |
1248 | gs_translationsOwned = true; | |
1249 | } | |
1250 | ||
1251 | /*static*/ | |
1252 | void wxTranslations::SetNonOwned(wxTranslations *t) | |
1253 | { | |
1254 | if ( gs_translationsOwned ) | |
1255 | delete gs_translations; | |
1256 | gs_translations = t; | |
1257 | gs_translationsOwned = false; | |
1258 | } | |
1259 | ||
1260 | ||
1261 | wxTranslations::wxTranslations() | |
1262 | { | |
1263 | m_pMsgCat = NULL; | |
1264 | m_loader = new wxFileTranslationsLoader; | |
1265 | } | |
1266 | ||
1267 | ||
1268 | wxTranslations::~wxTranslations() | |
1269 | { | |
1270 | delete m_loader; | |
1271 | ||
1272 | // free catalogs memory | |
1273 | wxMsgCatalog *pTmpCat; | |
1274 | while ( m_pMsgCat != NULL ) | |
1275 | { | |
1276 | pTmpCat = m_pMsgCat; | |
1277 | m_pMsgCat = m_pMsgCat->m_pNext; | |
1278 | delete pTmpCat; | |
1279 | } | |
1280 | } | |
1281 | ||
1282 | ||
1283 | void wxTranslations::SetLoader(wxTranslationsLoader *loader) | |
1284 | { | |
1285 | wxCHECK_RET( loader, "loader can't be NULL" ); | |
1286 | ||
1287 | delete m_loader; | |
1288 | m_loader = loader; | |
1289 | } | |
1290 | ||
1291 | ||
1292 | void wxTranslations::SetLanguage(wxLanguage lang) | |
1293 | { | |
1294 | if ( lang == wxLANGUAGE_DEFAULT ) | |
1295 | SetLanguage(""); | |
1296 | else | |
1297 | SetLanguage(wxLocale::GetLanguageCanonicalName(lang)); | |
1298 | } | |
1299 | ||
1300 | void wxTranslations::SetLanguage(const wxString& lang) | |
1301 | { | |
1302 | m_lang = lang; | |
1303 | } | |
1304 | ||
1305 | ||
1306 | bool wxTranslations::AddStdCatalog() | |
1307 | { | |
1308 | if ( !AddCatalog(wxS("wxstd")) ) | |
1309 | return false; | |
1310 | ||
1311 | // there may be a catalog with toolkit specific overrides, it is not | |
1312 | // an error if this does not exist | |
1313 | wxString port(wxPlatformInfo::Get().GetPortIdName()); | |
1314 | if ( !port.empty() ) | |
1315 | { | |
1316 | AddCatalog(port.BeforeFirst(wxS('/')).MakeLower()); | |
1317 | } | |
1318 | ||
1319 | return true; | |
1320 | } | |
1321 | ||
1322 | ||
1323 | bool wxTranslations::AddCatalog(const wxString& domain) | |
1324 | { | |
1325 | return AddCatalog(domain, wxLANGUAGE_ENGLISH_US); | |
1326 | } | |
1327 | ||
1328 | #if !wxUSE_UNICODE | |
1329 | bool wxTranslations::AddCatalog(const wxString& domain, | |
1330 | wxLanguage msgIdLanguage, | |
1331 | const wxString& msgIdCharset) | |
1332 | { | |
1333 | m_msgIdCharset[domain] = msgIdCharset; | |
1334 | return AddCatalog(domain, msgIdLanguage); | |
1335 | } | |
1336 | #endif // !wxUSE_UNICODE | |
1337 | ||
1338 | bool wxTranslations::AddCatalog(const wxString& domain, | |
1339 | wxLanguage msgIdLanguage) | |
1340 | { | |
1341 | const wxString msgIdLang = wxLocale::GetLanguageCanonicalName(msgIdLanguage); | |
1342 | const wxString domain_lang = ChooseLanguageForDomain(domain, msgIdLang); | |
1343 | ||
1344 | if ( domain_lang.empty() ) | |
1345 | { | |
1346 | wxLogTrace(TRACE_I18N, | |
1347 | wxS("no suitable translation for domain '%s' found"), | |
1348 | domain); | |
1349 | return false; | |
1350 | } | |
1351 | ||
1352 | wxLogTrace(TRACE_I18N, | |
1353 | wxS("adding '%s' translation for domain '%s' (msgid language '%s')"), | |
1354 | domain_lang, domain, msgIdLang); | |
1355 | ||
1356 | // It is OK to not load catalog if the msgid language and m_language match, | |
1357 | // in which case we can directly display the texts embedded in program's | |
1358 | // source code: | |
1359 | if ( msgIdLang == domain_lang ) | |
1360 | return true; | |
1361 | ||
1362 | wxCHECK_MSG( m_loader, false, "loader can't be NULL" ); | |
1363 | return m_loader->LoadCatalog(this, domain, domain_lang); | |
1364 | } | |
1365 | ||
1366 | ||
1367 | // check if the given catalog is loaded | |
1368 | bool wxTranslations::IsLoaded(const wxString& domain) const | |
1369 | { | |
1370 | return FindCatalog(domain) != NULL; | |
1371 | } | |
1372 | ||
1373 | ||
1374 | bool wxTranslations::LoadCatalogFile(const wxString& filename, | |
1375 | const wxString& domain) | |
1376 | { | |
1377 | wxMsgCatalog *pMsgCat = new wxMsgCatalog; | |
1378 | ||
1379 | #if wxUSE_UNICODE | |
1380 | const bool ok = pMsgCat->Load(filename, domain, wxEmptyString/*unused*/); | |
1381 | #else | |
1382 | const bool ok = pMsgCat->Load(filename, domain, | |
1383 | m_msgIdCharset[domain]); | |
1384 | #endif | |
1385 | ||
1386 | if ( !ok ) | |
1387 | { | |
1388 | // don't add it because it couldn't be loaded anyway | |
1389 | delete pMsgCat; | |
1390 | return false; | |
1391 | } | |
1392 | ||
1393 | // add it to the head of the list so that in GetString it will | |
1394 | // be searched before the catalogs added earlier | |
1395 | pMsgCat->m_pNext = m_pMsgCat; | |
1396 | m_pMsgCat = pMsgCat; | |
1397 | ||
1398 | return true; | |
1399 | } | |
1400 | ||
1401 | ||
1402 | wxString wxTranslations::ChooseLanguageForDomain(const wxString& WXUNUSED(domain), | |
1403 | const wxString& WXUNUSED(msgIdLang)) | |
1404 | { | |
1405 | // explicitly set language should always be respected | |
1406 | if ( !m_lang.empty() ) | |
1407 | return m_lang; | |
1408 | ||
1409 | // TODO: if the default language is used, pick the best (by comparing | |
1410 | // available languages with user's preferences), instead of blindly | |
1411 | // trusting availability of system language translation | |
1412 | return wxLocale::GetLanguageCanonicalName(wxLocale::GetSystemLanguage()); | |
1413 | } | |
1414 | ||
1415 | ||
1416 | namespace | |
1417 | { | |
1418 | WX_DECLARE_HASH_SET(wxString, wxStringHash, wxStringEqual, | |
1419 | wxLocaleUntranslatedStrings); | |
1420 | } | |
1421 | ||
1422 | /* static */ | |
1423 | const wxString& wxTranslations::GetUntranslatedString(const wxString& str) | |
1424 | { | |
1425 | static wxLocaleUntranslatedStrings s_strings; | |
1426 | ||
1427 | wxLocaleUntranslatedStrings::iterator i = s_strings.find(str); | |
1428 | if ( i == s_strings.end() ) | |
1429 | return *s_strings.insert(str).first; | |
1430 | ||
1431 | return *i; | |
1432 | } | |
1433 | ||
1434 | ||
1435 | const wxString& wxTranslations::GetString(const wxString& origString, | |
1436 | const wxString& domain) const | |
1437 | { | |
1438 | return GetString(origString, origString, size_t(-1), domain); | |
1439 | } | |
1440 | ||
1441 | const wxString& wxTranslations::GetString(const wxString& origString, | |
1442 | const wxString& origString2, | |
1443 | size_t n, | |
1444 | const wxString& domain) const | |
1445 | { | |
1446 | if ( origString.empty() ) | |
1447 | return GetUntranslatedString(origString); | |
1448 | ||
1449 | const wxString *trans = NULL; | |
1450 | wxMsgCatalog *pMsgCat; | |
1451 | ||
1452 | if ( !domain.empty() ) | |
1453 | { | |
1454 | pMsgCat = FindCatalog(domain); | |
1455 | ||
1456 | // does the catalog exist? | |
1457 | if ( pMsgCat != NULL ) | |
1458 | trans = pMsgCat->GetString(origString, n); | |
1459 | } | |
1460 | else | |
1461 | { | |
1462 | // search in all domains | |
1463 | for ( pMsgCat = m_pMsgCat; pMsgCat != NULL; pMsgCat = pMsgCat->m_pNext ) | |
1464 | { | |
1465 | trans = pMsgCat->GetString(origString, n); | |
1466 | if ( trans != NULL ) // take the first found | |
1467 | break; | |
1468 | } | |
1469 | } | |
1470 | ||
1471 | if ( trans == NULL ) | |
1472 | { | |
1473 | wxLogTrace | |
1474 | ( | |
1475 | TRACE_I18N, | |
1476 | "string \"%s\"%s not found in %slocale '%s'.", | |
1477 | origString, | |
1478 | ((long)n) != -1 ? wxString::Format("[%ld]", (long)n) : wxString(), | |
1479 | !domain.empty() ? wxString::Format("domain '%s' ", domain) : wxString(), | |
1480 | m_lang | |
1481 | ); | |
1482 | ||
1483 | if (n == size_t(-1)) | |
1484 | return GetUntranslatedString(origString); | |
1485 | else | |
1486 | return GetUntranslatedString(n == 1 ? origString : origString2); | |
1487 | } | |
1488 | ||
1489 | return *trans; | |
1490 | } | |
1491 | ||
1492 | ||
1493 | wxString wxTranslations::GetHeaderValue(const wxString& header, | |
1494 | const wxString& domain) const | |
1495 | { | |
1496 | if ( header.empty() ) | |
1497 | return wxEmptyString; | |
1498 | ||
1499 | const wxString *trans = NULL; | |
1500 | wxMsgCatalog *pMsgCat; | |
1501 | ||
1502 | if ( !domain.empty() ) | |
1503 | { | |
1504 | pMsgCat = FindCatalog(domain); | |
1505 | ||
1506 | // does the catalog exist? | |
1507 | if ( pMsgCat == NULL ) | |
1508 | return wxEmptyString; | |
1509 | ||
1510 | trans = pMsgCat->GetString(wxEmptyString, (size_t)-1); | |
1511 | } | |
1512 | else | |
1513 | { | |
1514 | // search in all domains | |
1515 | for ( pMsgCat = m_pMsgCat; pMsgCat != NULL; pMsgCat = pMsgCat->m_pNext ) | |
1516 | { | |
1517 | trans = pMsgCat->GetString(wxEmptyString, (size_t)-1); | |
1518 | if ( trans != NULL ) // take the first found | |
1519 | break; | |
1520 | } | |
1521 | } | |
1522 | ||
1523 | if ( !trans || trans->empty() ) | |
1524 | return wxEmptyString; | |
1525 | ||
1526 | size_t found = trans->find(header); | |
1527 | if ( found == wxString::npos ) | |
1528 | return wxEmptyString; | |
1529 | ||
1530 | found += header.length() + 2 /* ': ' */; | |
1531 | ||
1532 | // Every header is separated by \n | |
1533 | ||
1534 | size_t endLine = trans->find(wxS('\n'), found); | |
1535 | size_t len = (endLine == wxString::npos) ? | |
1536 | wxString::npos : (endLine - found); | |
1537 | ||
1538 | return trans->substr(found, len); | |
1539 | } | |
1540 | ||
1541 | ||
1542 | // find catalog by name in a linked list, return NULL if !found | |
1543 | wxMsgCatalog *wxTranslations::FindCatalog(const wxString& domain) const | |
1544 | { | |
1545 | // linear search in the linked list | |
1546 | wxMsgCatalog *pMsgCat; | |
1547 | for ( pMsgCat = m_pMsgCat; pMsgCat != NULL; pMsgCat = pMsgCat->m_pNext ) | |
1548 | { | |
1549 | if ( pMsgCat->GetDomain() == domain ) | |
1550 | return pMsgCat; | |
1551 | } | |
1552 | ||
1553 | return NULL; | |
1554 | } | |
1555 | ||
1556 | // ---------------------------------------------------------------------------- | |
1557 | // wxFileTranslationsLoader | |
1558 | // ---------------------------------------------------------------------------- | |
1559 | ||
1560 | namespace | |
1561 | { | |
1562 | ||
1563 | // the list of the directories to search for message catalog files | |
1564 | wxArrayString gs_searchPrefixes; | |
1565 | ||
1566 | // return the directories to search for message catalogs under the given | |
1567 | // prefix, separated by wxPATH_SEP | |
1568 | wxString GetMsgCatalogSubdirs(const wxString& prefix, const wxString& lang) | |
1569 | { | |
1570 | // Search first in Unix-standard prefix/lang/LC_MESSAGES, then in | |
1571 | // prefix/lang and finally in just prefix. | |
1572 | // | |
1573 | // Note that we use LC_MESSAGES on all platforms and not just Unix, because | |
1574 | // it doesn't cost much to look into one more directory and doing it this | |
1575 | // way has two important benefits: | |
1576 | // a) we don't break compatibility with wx-2.6 and older by stopping to | |
1577 | // look in a directory where the catalogs used to be and thus silently | |
1578 | // breaking apps after they are recompiled against the latest wx | |
1579 | // b) it makes it possible to package app's support files in the same | |
1580 | // way on all target platforms | |
1581 | const wxString pathPrefix = wxFileName(prefix, lang).GetFullPath(); | |
1582 | ||
1583 | wxString searchPath; | |
1584 | searchPath.reserve(4*pathPrefix.length()); | |
1585 | searchPath << pathPrefix << wxFILE_SEP_PATH << "LC_MESSAGES" << wxPATH_SEP | |
1586 | << prefix << wxFILE_SEP_PATH << wxPATH_SEP | |
1587 | << pathPrefix; | |
1588 | ||
1589 | return searchPath; | |
1590 | } | |
1591 | ||
1592 | // construct the search path for the given language | |
1593 | static wxString GetFullSearchPath(const wxString& lang) | |
1594 | { | |
1595 | // first take the entries explicitly added by the program | |
1596 | wxArrayString paths; | |
1597 | paths.reserve(gs_searchPrefixes.size() + 1); | |
1598 | size_t n, | |
1599 | count = gs_searchPrefixes.size(); | |
1600 | for ( n = 0; n < count; n++ ) | |
1601 | { | |
1602 | paths.Add(GetMsgCatalogSubdirs(gs_searchPrefixes[n], lang)); | |
1603 | } | |
1604 | ||
1605 | ||
1606 | #if wxUSE_STDPATHS | |
1607 | // then look in the standard location | |
1608 | const wxString stdp = wxStandardPaths::Get(). | |
1609 | GetLocalizedResourcesDir(lang, wxStandardPaths::ResourceCat_Messages); | |
1610 | ||
1611 | if ( paths.Index(stdp) == wxNOT_FOUND ) | |
1612 | paths.Add(stdp); | |
1613 | #endif // wxUSE_STDPATHS | |
1614 | ||
1615 | // last look in default locations | |
1616 | #ifdef __UNIX__ | |
1617 | // LC_PATH is a standard env var containing the search path for the .mo | |
1618 | // files | |
1619 | const char *pszLcPath = wxGetenv("LC_PATH"); | |
1620 | if ( pszLcPath ) | |
1621 | { | |
1622 | const wxString lcp = GetMsgCatalogSubdirs(pszLcPath, lang); | |
1623 | if ( paths.Index(lcp) == wxNOT_FOUND ) | |
1624 | paths.Add(lcp); | |
1625 | } | |
1626 | ||
1627 | // also add the one from where wxWin was installed: | |
1628 | wxString wxp = wxGetInstallPrefix(); | |
1629 | if ( !wxp.empty() ) | |
1630 | { | |
1631 | wxp = GetMsgCatalogSubdirs(wxp + wxS("/share/locale"), lang); | |
1632 | if ( paths.Index(wxp) == wxNOT_FOUND ) | |
1633 | paths.Add(wxp); | |
1634 | } | |
1635 | #endif // __UNIX__ | |
1636 | ||
1637 | ||
1638 | // finally construct the full search path | |
1639 | wxString searchPath; | |
1640 | searchPath.reserve(500); | |
1641 | count = paths.size(); | |
1642 | for ( n = 0; n < count; n++ ) | |
1643 | { | |
1644 | searchPath += paths[n]; | |
1645 | if ( n != count - 1 ) | |
1646 | searchPath += wxPATH_SEP; | |
1647 | } | |
1648 | ||
1649 | return searchPath; | |
1650 | } | |
1651 | ||
1652 | } // anonymous namespace | |
1653 | ||
1654 | ||
1655 | void wxFileTranslationsLoader::AddCatalogLookupPathPrefix(const wxString& prefix) | |
1656 | { | |
1657 | if ( gs_searchPrefixes.Index(prefix) == wxNOT_FOUND ) | |
1658 | { | |
1659 | gs_searchPrefixes.Add(prefix); | |
1660 | } | |
1661 | //else: already have it | |
1662 | } | |
1663 | ||
1664 | ||
1665 | bool wxFileTranslationsLoader::LoadCatalog(wxTranslations *translations, | |
1666 | const wxString& domain, | |
1667 | const wxString& lang) | |
1668 | { | |
1669 | wxCHECK_MSG( lang.length() >= LEN_LANG, false, | |
1670 | "invalid language specification" ); | |
1671 | ||
1672 | wxString searchPath; | |
1673 | ||
1674 | #if wxUSE_FONTMAP | |
1675 | // first look for the catalog for this language and the current locale: | |
1676 | // notice that we don't use the system name for the locale as this would | |
1677 | // force us to install catalogs in different locations depending on the | |
1678 | // system but always use the canonical name | |
1679 | wxFontEncoding encSys = wxLocale::GetSystemEncoding(); | |
1680 | if ( encSys != wxFONTENCODING_SYSTEM ) | |
1681 | { | |
1682 | wxString fullname(lang); | |
1683 | fullname << wxS('.') << wxFontMapperBase::GetEncodingName(encSys); | |
1684 | searchPath << GetFullSearchPath(fullname) << wxPATH_SEP; | |
1685 | } | |
1686 | #endif // wxUSE_FONTMAP | |
1687 | ||
1688 | searchPath += GetFullSearchPath(lang); | |
1689 | if ( lang.length() > LEN_LANG && lang[LEN_LANG] == wxS('_') ) | |
1690 | { | |
1691 | // also add just base locale name: for things like "fr_BE" (Belgium | |
1692 | // French) we should use fall back on plain "fr" if no Belgium-specific | |
1693 | // message catalogs exist | |
1694 | searchPath << wxPATH_SEP | |
1695 | << GetFullSearchPath(ExtractLang(lang)); | |
1696 | } | |
1697 | ||
1698 | wxLogTrace(TRACE_I18N, wxS("Looking for \"%s.mo\" in search path \"%s\""), | |
1699 | domain, searchPath); | |
1700 | ||
1701 | wxFileName fn(domain); | |
1702 | fn.SetExt(wxS("mo")); | |
1703 | ||
1704 | wxString strFullName; | |
1705 | if ( !wxFindFileInPath(&strFullName, searchPath, fn.GetFullPath()) ) | |
1706 | { | |
1707 | wxLogVerbose(_("catalog file for domain '%s' not found."), domain); | |
1708 | wxLogTrace(TRACE_I18N, wxS("Catalog \"%s.mo\" not found"), domain); | |
1709 | return false; | |
1710 | } | |
1711 | ||
1712 | // open file and read its data | |
1713 | wxLogVerbose(_("using catalog '%s' from '%s'."), domain, strFullName.c_str()); | |
1714 | wxLogTrace(TRACE_I18N, wxS("Using catalog \"%s\"."), strFullName.c_str()); | |
1715 | ||
1716 | return translations->LoadCatalogFile(strFullName, domain); | |
1717 | } | |
1718 | ||
1719 | // ---------------------------------------------------------------------------- | |
1720 | // wxTranslationsModule module (for destruction of gs_translations) | |
1721 | // ---------------------------------------------------------------------------- | |
1722 | ||
1723 | class wxTranslationsModule: public wxModule | |
1724 | { | |
1725 | DECLARE_DYNAMIC_CLASS(wxTranslationsModule) | |
1726 | public: | |
1727 | wxTranslationsModule() {} | |
1728 | ||
1729 | bool OnInit() | |
1730 | { | |
1731 | return true; | |
1732 | } | |
1733 | ||
1734 | void OnExit() | |
1735 | { | |
1736 | if ( gs_translationsOwned ) | |
1737 | delete gs_translations; | |
1738 | gs_translations = NULL; | |
1739 | gs_translationsOwned = true; | |
1740 | } | |
1741 | }; | |
1742 | ||
1743 | IMPLEMENT_DYNAMIC_CLASS(wxTranslationsModule, wxModule) | |
1744 | ||
1745 | #endif // wxUSE_INTL |