Coverage Report

Created: 2023-11-19 07:06

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