LibreOffice Module unotools (master) 1
resmgr.cxx
Go to the documentation of this file.
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>
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)
66#include <prewin.h>
67#include <crtdbg.h>
68#include <postwin.h>
69#endif
70
71namespace
72{
73 OUString createFromUtf8(const char* data, size_t size)
74 {
75 OUString aTarget;
76 bool bSuccess = rtl_convertStringToUString(&aTarget.pData,
77 data,
78 size,
79 RTL_TEXTENCODING_UTF8,
80 RTL_TEXTTOUNICODE_FLAGS_UNDEFINED_ERROR|RTL_TEXTTOUNICODE_FLAGS_MBUNDEFINED_ERROR|RTL_TEXTTOUNICODE_FLAGS_INVALID_ERROR);
81 (void) bSuccess;
82 assert(bSuccess);
83 return aTarget;
84 }
85
86 OString genKeyId(const OString& rGenerator)
87 {
88 sal_uInt32 nCRC = rtl_crc32(0, rGenerator.getStr(), rGenerator.getLength());
89 // Use simple ASCII characters, exclude I, l, 1 and O, 0 to avoid confusing IDs
90 static const char sSymbols[] =
91 "ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz23456789";
92 char sKeyId[6];
93 for (short nKeyInd = 0; nKeyInd < 5; ++nKeyInd)
94 {
95 sKeyId[nKeyInd] = sSymbols[(nCRC & 63) % strlen(sSymbols)];
96 nCRC >>= 6;
97 }
98 sKeyId[5] = '\0';
99 return sKeyId;
100 }
101}
102
103#if defined(_WIN32) && defined(DBG_UTIL)
104static int IgnoringCrtReportHook(int reportType, wchar_t *message, int * /* returnValue */)
105{
106 OUString sType;
107 if (reportType == _CRT_WARN)
108 sType = "WARN";
109 else if (reportType == _CRT_ERROR)
110 sType = "ERROR";
111 else if (reportType == _CRT_ASSERT)
112 sType = "ASSERT";
113 else
114 sType = "?(" + OUString::number(reportType) + ")";
115
116 SAL_WARN("unotools.i18n", "CRT Report Hook: " << sType << ": " << OUString(o3tl::toU(message)));
117
118 return TRUE;
119}
120#endif
121
122
123namespace Translate
124{
125 std::locale Create(std::string_view aPrefixName, const LanguageTag& rLocale)
126 {
127 static std::unordered_map<OString, std::locale> aCache;
128 OString sIdentifier = rLocale.getGlibcLocaleString(u".UTF-8").toUtf8();
129 OString sUnique = sIdentifier + aPrefixName;
130 auto aFind = aCache.find(sUnique);
131 if (aFind != aCache.end())
132 return aFind->second;
133 boost::locale::generator gen;
134#if BOOST_VERSION < 108100
135 gen.characters(boost::locale::char_facet);
136 gen.categories(boost::locale::message_facet | boost::locale::information_facet);
137#else
138 gen.characters(boost::locale::char_facet_t::char_f);
139 gen.categories(boost::locale::category_t::message | boost::locale::category_t::information);
140#endif
141#if defined(ANDROID) || defined(EMSCRIPTEN)
142 OString sPath(OString(lo_get_app_data_dir()) + "/program/resource");
143#else
144 OUString uri("$BRAND_BASE_DIR/$BRAND_SHARE_RESOURCE_SUBDIR/");
145 rtl::Bootstrap::expandMacros(uri);
146 OUString path;
147 osl::File::getSystemPathFromFileURL(uri, path);
148#if defined _WIN32
149 // add_messages_path is documented to treat path string in the *created* locale's encoding
150 // on Windows; creating an UTF-8 encoding, we're lucky to have Unicode path support here.
151 constexpr rtl_TextEncoding eEncoding = RTL_TEXTENCODING_UTF8;
152#else
153 const rtl_TextEncoding eEncoding = osl_getThreadTextEncoding();
154#endif
155 OString sPath(OUStringToOString(path, eEncoding));
156#endif
157 gen.add_messages_path(std::string(sPath));
158#if defined UNX && !defined MACOSX && !defined IOS && !defined ANDROID && !defined EMSCRIPTEN
159 // allow gettext to find these .mo files e.g. so gtk dialogs can use them
160 bindtextdomain(aPrefixName.data(), sPath.getStr());
161 // tdf#131069 gtk, and anything sane, always wants utf-8 strings as output
162 bind_textdomain_codeset(aPrefixName.data(), "UTF-8");
163#endif
164 gen.add_messages_domain(aPrefixName.data());
165
166#if defined(_WIN32) && defined(DBG_UTIL)
167 // With a newer C++ debug runtime (in an --enable-dbgutil build), passing an invalid locale
168 // name causes an attempt to display an error dialog. Which does not even show up, at least
169 // for me, but instead the process (gengal, at least) just hangs. Which is far from ideal.
170
171 // Passing a POSIX-style locale name to the std::locale constructor on Windows is a bit odd,
172 // but apparently in the normal C++ runtime it "just" causes an exception to be thrown, that
173 // boost catches (see the loadable(std::string name) in boost's
174 // libs\locale\src\std\std_backend.cpp), and then instead uses the Windows style locale name
175 // it knows how to construct. (Why does it even try the POSIX style name I can't
176 // understand.)
177
178 // Actually it isn't just the locale name part "en_US" of a locale like "en_US.UTF-8" that
179 // is problematic, but also the encoding part, "UTF-8". The Microsoft C/C++ library does not
180 // support UTF-8 locales. The error message that our own report hook catches says:
181 // "f:\dd\vctools\crt\crtw32\stdcpp\xmbtowc.c(89) : Assertion failed: ploc->_Mbcurmax == 1
182 // || ploc->_Mbcurmax == 2". Clearly in a UTF-8 locale (perhaps one that boost internally
183 // constructs?) the maximum bytes per character will be more than 2.
184
185 // With a debug C++ runtime, we need to avoid the error dialog, and just ignore the error.
186
187 struct CrtSetReportHook
188 {
189 int mnCrtSetReportHookSucceeded;
190
191 CrtSetReportHook()
192 {
193 mnCrtSetReportHookSucceeded = _CrtSetReportHookW2(_CRT_RPTHOOK_INSTALL, IgnoringCrtReportHook);
194 }
195
196 ~CrtSetReportHook()
197 {
198 if (mnCrtSetReportHookSucceeded >= 0)
199 _CrtSetReportHookW2(_CRT_RPTHOOK_REMOVE, IgnoringCrtReportHook);
200 }
201 } aHook;
202
203#endif
204
205 std::locale aRet(gen(std::string(sIdentifier)));
206
207 aCache[sUnique] = aRet;
208 return aRet;
209 }
210
211 OUString get(TranslateId sContextAndId, const std::locale &loc)
212 {
213 assert(!strchr(sContextAndId.mpId, '\004') && "should be using nget, not get");
214
215 //if it's a key id locale, generate it here
216 if (std::use_facet<boost::locale::info>(loc).language() == "qtz")
217 {
218 OString sKeyId(genKeyId(OString::Concat(sContextAndId.mpContext) + "|" + std::string_view(sContextAndId.mpId)));
219 return OUString::fromUtf8(sKeyId) + u"\u2016" + createFromUtf8(sContextAndId.mpId, strlen(sContextAndId.mpId));
220 }
221
222 //otherwise translate it
223 const std::string ret = boost::locale::pgettext(sContextAndId.mpContext, sContextAndId.mpId, loc);
224 OUString result(ExpandVariables(createFromUtf8(ret.data(), ret.size())));
225
227 {
228 // If it is de-CH, change sharp s to double s.
229 if (std::use_facet<boost::locale::info>(loc).country() == "CH" &&
230 std::use_facet<boost::locale::info>(loc).language() == "de")
231 result = result.replaceAll(OUString::fromUtf8("\xC3\x9F"), "ss");
232 }
233 return result;
234 }
235
236 OUString nget(TranslateNId aContextSingularPlural, int n, const std::locale &loc)
237 {
238 //if it's a key id locale, generate it here
239 if (std::use_facet<boost::locale::info>(loc).language() == "qtz")
240 {
241 OString sKeyId(genKeyId(OString::Concat(aContextSingularPlural.mpContext) + "|" + aContextSingularPlural.mpSingular));
242 const char* pForm = n == 0 ? aContextSingularPlural.mpSingular : aContextSingularPlural.mpPlural;
243 return OUString::fromUtf8(sKeyId) + u"\u2016" + createFromUtf8(pForm, strlen(pForm));
244 }
245
246 //otherwise translate it
247 const std::string ret = boost::locale::npgettext(aContextSingularPlural.mpContext, aContextSingularPlural.mpSingular, aContextSingularPlural.mpPlural, n, loc);
248 OUString result(ExpandVariables(createFromUtf8(ret.data(), ret.size())));
249
251 {
252 if (std::use_facet<boost::locale::info>(loc).country() == "CH" &&
253 std::use_facet<boost::locale::info>(loc).language() == "de")
254 result = result.replaceAll(OUString::fromUtf8("\xC3\x9F"), "ss");
255 }
256 return result;
257 }
258
260
261 OUString ExpandVariables(const OUString& rString)
262 {
264 return pImplResHookProc(rString);
265 return rString;
266 }
267
269 {
270 pImplResHookProc = pProc;
271 }
272
274 {
275 return pImplResHookProc;
276 }
277}
278
279bool TranslateId::operator==(const TranslateId& other) const
280{
281 if (mpContext == nullptr || other.mpContext == nullptr)
282 {
283 if (mpContext != other.mpContext)
284 return false;
285 }
286 else if (strcmp(mpContext, other.mpContext) != 0)
287 return false;
288
289 if (mpId == nullptr || other.mpId == nullptr)
290 {
291 return mpId == other.mpId;
292 }
293 return strcmp(mpId,other.mpId) == 0;
294}
295
297{
298 if (mpContext == nullptr || other.mpContext == nullptr)
299 {
300 if (mpContext != other.mpContext)
301 return false;
302 }
303 else if (strcmp(mpContext, other.mpContext) != 0)
304 return false;
305
306 if (mpSingular == nullptr || other.mpSingular == nullptr)
307 {
308 if (mpSingular != other.mpSingular)
309 return false;
310 }
311 else if (strcmp(mpSingular, other.mpSingular) != 0)
312 return false;
313
314 if (mpPlural == nullptr || other.mpPlural == nullptr)
315 {
316 return mpPlural == other.mpPlural;
317 }
318 return strcmp(mpPlural,other.mpPlural) == 0;
319}
320
321/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
OptionalString sType
OUString getGlibcLocaleString(std::u16string_view rEncoding) const
float u
#define TRUE
sal_Int64 n
#define SAL_WARN(area, stream)
OUString nget(TranslateNId aContextSingularPlural, int n, const std::locale &loc)
Definition: resmgr.cxx:236
static ResHookProc pImplResHookProc
Definition: resmgr.cxx:259
ResHookProc GetReadStringHook()
Definition: resmgr.cxx:273
std::locale Create(std::string_view aPrefixName, const LanguageTag &rLocale)
Definition: resmgr.cxx:125
OUString ExpandVariables(const OUString &rString)
Definition: resmgr.cxx:261
OUString get(TranslateId sContextAndId, const std::locale &loc)
Definition: resmgr.cxx:211
void SetReadStringHook(ResHookProc pProc)
Definition: resmgr.cxx:268
OString OUStringToOString(std::u16string_view str, ConnectionSettings const *settings)
OUString(* ResHookProc)(const OUString &rStr)
Definition: resmgr.hxx:29
bool operator==(const TranslateId &other) const
Definition: resmgr.cxx:279
const char * mpId
Definition: resmgr.hxx:34
const char * mpContext
Definition: resmgr.hxx:33
const char * mpPlural
Definition: resmgr.hxx:51
const char * mpSingular
Definition: resmgr.hxx:50
const char * mpContext
Definition: resmgr.hxx:49
bool operator==(const TranslateNId &other) const
Definition: resmgr.cxx:296
Any result