Coverage Report

Created: 2025-07-07 10:01

/src/libreoffice/unotools/source/i18n/resmgr.cxx
Line
Count
Source (jump to first uncovered line)
1
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */
2
/*
3
 * This file is part of the LibreOffice project.
4
 *
5
 * This Source Code Form is subject to the terms of the Mozilla Public
6
 * License, v. 2.0. If a copy of the MPL was not distributed with this
7
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
8
 *
9
 * This file incorporates work covered by the following license notice:
10
 *
11
 *   Licensed to the Apache Software Foundation (ASF) under one or more
12
 *   contributor license agreements. See the NOTICE file distributed
13
 *   with this work for additional information regarding copyright
14
 *   ownership. The ASF licenses this file to you under the Apache
15
 *   License, Version 2.0 (the "License"); you may not use this file
16
 *   except in compliance with the License. You may obtain a copy of
17
 *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
18
 */
19
20
#include <boost/version.hpp>
21
#if BOOST_VERSION < 106700
22
// Needed when #include <boost/locale.hpp> below includes Boost 1.65.1
23
// workdir/UnpackedTarball/boost/boost/locale/format.hpp using "std::auto_ptr<data> d;", but must
24
// come very early here in case <memory> is already (indirectly) included earlier:
25
#include <config_libcxx.h>
26
#if HAVE_LIBCPP
27
#define _LIBCPP_ENABLE_CXX17_REMOVED_AUTO_PTR
28
#elif defined _MSC_VER
29
#define _HAS_AUTO_PTR_ETC 1
30
#endif
31
#endif
32
33
#include <sal/config.h>
34
35
#include <cassert>
36
37
#include <string.h>
38
#include <stdio.h>
39
#if defined UNX && !defined MACOSX && !defined IOS && !defined ANDROID && !defined EMSCRIPTEN
40
#   include <libintl.h>
41
#endif
42
43
#include <comphelper/lok.hxx>
44
#include <unotools/resmgr.hxx>
45
#include <osl/thread.h>
46
#include <osl/file.hxx>
47
#include <rtl/crc.h>
48
#include <rtl/bootstrap.hxx>
49
#include <i18nlangtag/languagetag.hxx>
50
51
#include <boost/locale.hpp>
52
#include <boost/locale/gnu_gettext.hpp>
53
54
#include <unordered_map>
55
56
#ifdef ANDROID
57
#include <osl/detail/android-bootstrap.h>
58
#endif
59
60
#ifdef EMSCRIPTEN
61
#include <osl/detail/emscripten-bootstrap.h>
62
#endif
63
64
#if defined(_WIN32) && defined(DBG_UTIL)
65
#include <o3tl/char16_t2wchar_t.hxx>
66
#include <prewin.h>
67
#include <crtdbg.h>
68
#include <postwin.h>
69
#endif
70
71
namespace
72
{
73
    OString genKeyId(const OString& rGenerator)
74
0
    {
75
0
        sal_uInt32 nCRC = rtl_crc32(0, rGenerator.getStr(), rGenerator.getLength());
76
        // Use simple ASCII characters, exclude I, l, 1 and O, 0 to avoid confusing IDs
77
0
        static const char sSymbols[] =
78
0
            "ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz23456789";
79
0
        char sKeyId[6];
80
0
        for (short nKeyInd = 0; nKeyInd < 5; ++nKeyInd)
81
0
        {
82
0
            sKeyId[nKeyInd] = sSymbols[(nCRC & 63) % strlen(sSymbols)];
83
0
            nCRC >>= 6;
84
0
        }
85
0
        sKeyId[5] = '\0';
86
0
        return sKeyId;
87
0
    }
88
}
89
90
#if defined(_WIN32) && defined(DBG_UTIL)
91
static int IgnoringCrtReportHook(int reportType, wchar_t *message, int * /* returnValue */)
92
{
93
    OUString sType;
94
    if (reportType == _CRT_WARN)
95
        sType = "WARN";
96
    else if (reportType == _CRT_ERROR)
97
        sType = "ERROR";
98
    else if (reportType == _CRT_ASSERT)
99
        sType = "ASSERT";
100
    else
101
        sType = "?(" + OUString::number(reportType) + ")";
102
103
    SAL_WARN("unotools.i18n", "CRT Report Hook: " << sType << ": " << OUString(o3tl::toU(message)));
104
105
    return TRUE;
106
}
107
#endif
108
109
110
namespace Translate
111
{
112
    std::locale Create(std::string_view aPrefixName, const LanguageTag& rLocale)
113
24.1M
    {
114
24.1M
        static std::unordered_map<OString, std::locale> aCache;
115
24.1M
        OString sIdentifier = rLocale.getGlibcLocaleString(u".UTF-8").toUtf8();
116
24.1M
        OString sUnique = sIdentifier + aPrefixName;
117
24.1M
        auto aFind = aCache.find(sUnique);
118
24.1M
        if (aFind != aCache.end())
119
24.1M
            return aFind->second;
120
153
        boost::locale::generator gen;
121
#if BOOST_VERSION < 108100
122
        gen.characters(boost::locale::char_facet);
123
        gen.categories(boost::locale::message_facet | boost::locale::information_facet);
124
#else
125
153
        gen.characters(boost::locale::char_facet_t::char_f);
126
153
        gen.categories(boost::locale::category_t::message | boost::locale::category_t::information);
127
153
#endif
128
#if defined(ANDROID) || defined(EMSCRIPTEN)
129
        OString sPath(OString(lo_get_app_data_dir()) + "/program/resource");
130
#else
131
153
        OUString uri(u"$BRAND_BASE_DIR/$BRAND_SHARE_RESOURCE_SUBDIR/"_ustr);
132
153
        rtl::Bootstrap::expandMacros(uri);
133
153
        OUString path;
134
153
        osl::File::getSystemPathFromFileURL(uri, path);
135
#if defined _WIN32
136
        // add_messages_path is documented to treat path string in the *created* locale's encoding
137
        // on Windows; creating an UTF-8 encoding, we're lucky to have Unicode path support here.
138
        constexpr rtl_TextEncoding eEncoding = RTL_TEXTENCODING_UTF8;
139
#else
140
153
        const rtl_TextEncoding eEncoding = osl_getThreadTextEncoding();
141
153
#endif
142
153
        OString sPath(OUStringToOString(path, eEncoding));
143
153
#endif
144
153
        gen.add_messages_path(std::string(sPath));
145
153
#if defined UNX && !defined MACOSX && !defined IOS && !defined ANDROID && !defined EMSCRIPTEN
146
        // allow gettext to find these .mo files e.g. so gtk dialogs can use them
147
153
        bindtextdomain(aPrefixName.data(), sPath.getStr());
148
        // tdf#131069 gtk, and anything sane, always wants utf-8 strings as output
149
153
        bind_textdomain_codeset(aPrefixName.data(), "UTF-8");
150
153
#endif
151
153
        gen.add_messages_domain(aPrefixName.data());
152
153
#if defined(_WIN32) && defined(DBG_UTIL)
154
        // With a newer C++ debug runtime (in an --enable-dbgutil build), passing an invalid locale
155
        // name causes an attempt to display an error dialog. Which does not even show up, at least
156
        // for me, but instead the process (gengal, at least) just hangs. Which is far from ideal.
157
158
        // Passing a POSIX-style locale name to the std::locale constructor on Windows is a bit odd,
159
        // but apparently in the normal C++ runtime it "just" causes an exception to be thrown, that
160
        // boost catches (see the loadable(std::string name) in boost's
161
        // libs\locale\src\std\std_backend.cpp), and then instead uses the Windows style locale name
162
        // it knows how to construct. (Why does it even try the POSIX style name I can't
163
        // understand.)
164
165
        // Actually it isn't just the locale name part "en_US" of a locale like "en_US.UTF-8" that
166
        // is problematic, but also the encoding part, "UTF-8". The Microsoft C/C++ library does not
167
        // support UTF-8 locales. The error message that our own report hook catches says:
168
        // "f:\dd\vctools\crt\crtw32\stdcpp\xmbtowc.c(89) : Assertion failed: ploc->_Mbcurmax == 1
169
        // || ploc->_Mbcurmax == 2". Clearly in a UTF-8 locale (perhaps one that boost internally
170
        // constructs?) the maximum bytes per character will be more than 2.
171
172
        // With a debug C++ runtime, we need to avoid the error dialog, and just ignore the error.
173
174
        struct CrtSetReportHook
175
        {
176
            int mnCrtSetReportHookSucceeded;
177
178
            CrtSetReportHook()
179
            {
180
                mnCrtSetReportHookSucceeded = _CrtSetReportHookW2(_CRT_RPTHOOK_INSTALL, IgnoringCrtReportHook);
181
            }
182
183
            ~CrtSetReportHook()
184
            {
185
                if (mnCrtSetReportHookSucceeded >= 0)
186
                    _CrtSetReportHookW2(_CRT_RPTHOOK_REMOVE, IgnoringCrtReportHook);
187
            }
188
        } aHook;
189
190
#endif
191
192
153
        std::locale aRet(gen(std::string(sIdentifier)));
193
194
153
        aCache[sUnique] = aRet;
195
153
        return aRet;
196
24.1M
    }
197
198
    OUString get(TranslateId sContextAndId, const std::locale &loc)
199
24.2M
    {
200
24.2M
        assert(!strchr(sContextAndId.getId(), '\004') && "should be using nget, not get");
201
202
        //if it's a key id locale, generate it here
203
24.2M
        if (std::has_facet<boost::locale::info>(loc)) {
204
24.2M
            if (std::use_facet<boost::locale::info>(loc).language() == "qtz")
205
0
            {
206
0
                OString sKeyId(genKeyId(OString::Concat(sContextAndId.mpContext) + "|" + std::string_view(sContextAndId.getId())));
207
0
                return OUString::fromUtf8(sKeyId) + u"\u2016" + OUString::fromUtf8(sContextAndId.getId());
208
0
            }
209
24.2M
        }
210
211
        //otherwise translate it
212
24.2M
        const std::string ret = boost::locale::pgettext(sContextAndId.mpContext, sContextAndId.getId(), loc);
213
24.2M
        OUString result(ExpandVariables(OUString::fromUtf8(ret.data())));
214
215
24.2M
        if (comphelper::LibreOfficeKit::isActive())
216
0
        {
217
            // If it is de-CH, change sharp s to double s.
218
0
            if (std::use_facet<boost::locale::info>(loc).country() == "CH" &&
219
0
                std::use_facet<boost::locale::info>(loc).language() == "de")
220
0
                result = result.replaceAll(OUString::fromUtf8("\xC3\x9F"), "ss");
221
0
        }
222
24.2M
        return result;
223
24.2M
    }
224
225
    OUString getLanguage(const std::locale& loc)
226
5
    {
227
5
        std::string lang = std::use_facet<boost::locale::info>(loc).name(); // en_US.UTF-8
228
5
        lang = lang.substr(0, lang.find('.')); // en_US
229
5
        return OUString::fromUtf8(lang.data());
230
5
    }
231
232
    OUString nget(TranslateNId aContextSingularPlural, int n, const std::locale &loc)
233
0
    {
234
        //if it's a key id locale, generate it here
235
0
        if (std::use_facet<boost::locale::info>(loc).language() == "qtz")
236
0
        {
237
0
            OString sKeyId(genKeyId(OString::Concat(aContextSingularPlural.mpContext) + "|" + aContextSingularPlural.mpSingular));
238
0
            const char* pForm = n == 0 ? aContextSingularPlural.mpSingular : aContextSingularPlural.mpPlural;
239
0
            return OUString::fromUtf8(sKeyId) + u"\u2016" + OUString::fromUtf8(pForm);
240
0
        }
241
242
        //otherwise translate it
243
0
        const std::string ret = boost::locale::npgettext(aContextSingularPlural.mpContext, aContextSingularPlural.mpSingular, aContextSingularPlural.mpPlural, n, loc);
244
0
        OUString result(ExpandVariables(OUString::fromUtf8(ret.data())));
245
246
0
        if (comphelper::LibreOfficeKit::isActive())
247
0
        {
248
0
            if (std::use_facet<boost::locale::info>(loc).country() == "CH" &&
249
0
                std::use_facet<boost::locale::info>(loc).language() == "de")
250
0
                result = result.replaceAll(OUString::fromUtf8("\xC3\x9F"), "ss");
251
0
        }
252
0
        return result;
253
0
    }
254
255
    static ResHookProc pImplResHookProc = nullptr;
256
257
    OUString ExpandVariables(const OUString& rString)
258
24.2M
    {
259
24.2M
        if (pImplResHookProc)
260
0
            return pImplResHookProc(rString);
261
24.2M
        return rString;
262
24.2M
    }
263
264
    void SetReadStringHook( ResHookProc pProc )
265
0
    {
266
0
        pImplResHookProc = pProc;
267
0
    }
268
269
    ResHookProc GetReadStringHook()
270
0
    {
271
0
        return pImplResHookProc;
272
0
    }
273
}
274
275
bool TranslateId::operator==(const TranslateId& other) const
276
3.74k
{
277
3.74k
    if (mpContext == nullptr || other.mpContext == nullptr)
278
0
    {
279
0
        if (mpContext != other.mpContext)
280
0
            return false;
281
0
    }
282
3.74k
    else if (strcmp(mpContext, other.mpContext) != 0)
283
3.23k
        return false;
284
285
512
    if (mpId == nullptr || other.mpId == nullptr)
286
0
    {
287
0
        return mpId == other.mpId;
288
0
    }
289
512
    return strcmp(getId(),other.getId()) == 0;
290
512
}
291
292
bool TranslateNId::operator==(const TranslateNId& other) const
293
0
{
294
0
    if (mpContext == nullptr || other.mpContext == nullptr)
295
0
    {
296
0
        if (mpContext != other.mpContext)
297
0
            return false;
298
0
    }
299
0
    else if (strcmp(mpContext, other.mpContext) != 0)
300
0
        return false;
301
302
0
    if (mpSingular == nullptr || other.mpSingular == nullptr)
303
0
    {
304
0
        if (mpSingular != other.mpSingular)
305
0
            return false;
306
0
    }
307
0
    else if (strcmp(mpSingular, other.mpSingular) != 0)
308
0
        return false;
309
310
0
    if (mpPlural == nullptr || other.mpPlural == nullptr)
311
0
    {
312
0
        return mpPlural == other.mpPlural;
313
0
    }
314
0
    return strcmp(mpPlural,other.mpPlural) == 0;
315
0
}
316
317
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */