docthemes: Save themes def. to a file when added to ColorSets
[LibreOffice.git] / unotools / source / i18n / resmgr.cxx
blobf93e943b628afb275eae740fee7c5ecad86c4661
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.
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/.
9 * This file incorporates work covered by the following license notice:
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 .
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
33 #include <sal/config.h>
35 #include <cassert>
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
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>
51 #include <boost/locale.hpp>
52 #include <boost/locale/gnu_gettext.hpp>
54 #include <unordered_map>
56 #ifdef ANDROID
57 #include <osl/detail/android-bootstrap.h>
58 #endif
60 #ifdef EMSCRIPTEN
61 #include <osl/detail/emscripten-bootstrap.h>
62 #endif
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
71 namespace
73 OString genKeyId(const OString& rGenerator)
75 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 static const char sSymbols[] =
78 "ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz23456789";
79 char sKeyId[6];
80 for (short nKeyInd = 0; nKeyInd < 5; ++nKeyInd)
82 sKeyId[nKeyInd] = sSymbols[(nCRC & 63) % strlen(sSymbols)];
83 nCRC >>= 6;
85 sKeyId[5] = '\0';
86 return sKeyId;
90 #if defined(_WIN32) && defined(DBG_UTIL)
91 static int IgnoringCrtReportHook(int reportType, wchar_t *message, int * /* returnValue */)
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) + ")";
103 SAL_WARN("unotools.i18n", "CRT Report Hook: " << sType << ": " << OUString(o3tl::toU(message)));
105 return TRUE;
107 #endif
110 namespace Translate
112 std::locale Create(std::string_view aPrefixName, const LanguageTag& rLocale)
114 static std::unordered_map<OString, std::locale> aCache;
115 OString sIdentifier = rLocale.getGlibcLocaleString(u".UTF-8").toUtf8();
116 OString sUnique = sIdentifier + aPrefixName;
117 auto aFind = aCache.find(sUnique);
118 if (aFind != aCache.end())
119 return aFind->second;
120 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 gen.characters(boost::locale::char_facet_t::char_f);
126 gen.categories(boost::locale::category_t::message | boost::locale::category_t::information);
127 #endif
128 #if defined(ANDROID) || defined(EMSCRIPTEN)
129 OString sPath(OString(lo_get_app_data_dir()) + "/program/resource");
130 #else
131 OUString uri(u"$BRAND_BASE_DIR/$BRAND_SHARE_RESOURCE_SUBDIR/"_ustr);
132 rtl::Bootstrap::expandMacros(uri);
133 OUString path;
134 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 const rtl_TextEncoding eEncoding = osl_getThreadTextEncoding();
141 #endif
142 OString sPath(OUStringToOString(path, eEncoding));
143 #endif
144 gen.add_messages_path(std::string(sPath));
145 #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 bindtextdomain(aPrefixName.data(), sPath.getStr());
148 // tdf#131069 gtk, and anything sane, always wants utf-8 strings as output
149 bind_textdomain_codeset(aPrefixName.data(), "UTF-8");
150 #endif
151 gen.add_messages_domain(aPrefixName.data());
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.
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.)
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.
172 // With a debug C++ runtime, we need to avoid the error dialog, and just ignore the error.
174 struct CrtSetReportHook
176 int mnCrtSetReportHookSucceeded;
178 CrtSetReportHook()
180 mnCrtSetReportHookSucceeded = _CrtSetReportHookW2(_CRT_RPTHOOK_INSTALL, IgnoringCrtReportHook);
183 ~CrtSetReportHook()
185 if (mnCrtSetReportHookSucceeded >= 0)
186 _CrtSetReportHookW2(_CRT_RPTHOOK_REMOVE, IgnoringCrtReportHook);
188 } aHook;
190 #endif
192 std::locale aRet(gen(std::string(sIdentifier)));
194 aCache[sUnique] = aRet;
195 return aRet;
198 OUString get(TranslateId sContextAndId, const std::locale &loc)
200 assert(!strchr(sContextAndId.getId(), '\004') && "should be using nget, not get");
202 //if it's a key id locale, generate it here
203 if (std::has_facet<boost::locale::info>(loc)) {
204 if (std::use_facet<boost::locale::info>(loc).language() == "qtz")
206 OString sKeyId(genKeyId(OString::Concat(sContextAndId.mpContext) + "|" + std::string_view(sContextAndId.getId())));
207 return OUString::fromUtf8(sKeyId) + u"\u2016" + OUString::fromUtf8(sContextAndId.getId());
211 //otherwise translate it
212 const std::string ret = boost::locale::pgettext(sContextAndId.mpContext, sContextAndId.getId(), loc);
213 OUString result(ExpandVariables(OUString::fromUtf8(ret.data())));
215 if (comphelper::LibreOfficeKit::isActive())
217 // If it is de-CH, change sharp s to double s.
218 if (std::use_facet<boost::locale::info>(loc).country() == "CH" &&
219 std::use_facet<boost::locale::info>(loc).language() == "de")
220 result = result.replaceAll(OUString::fromUtf8("\xC3\x9F"), "ss");
222 return result;
225 OUString getLanguage(const std::locale& loc)
227 std::string lang = std::use_facet<boost::locale::info>(loc).name(); // en_US.UTF-8
228 lang = lang.substr(0, lang.find('.')); // en_US
229 return OUString::fromUtf8(lang.data());
232 OUString nget(TranslateNId aContextSingularPlural, int n, const std::locale &loc)
234 //if it's a key id locale, generate it here
235 if (std::use_facet<boost::locale::info>(loc).language() == "qtz")
237 OString sKeyId(genKeyId(OString::Concat(aContextSingularPlural.mpContext) + "|" + aContextSingularPlural.mpSingular));
238 const char* pForm = n == 0 ? aContextSingularPlural.mpSingular : aContextSingularPlural.mpPlural;
239 return OUString::fromUtf8(sKeyId) + u"\u2016" + OUString::fromUtf8(pForm);
242 //otherwise translate it
243 const std::string ret = boost::locale::npgettext(aContextSingularPlural.mpContext, aContextSingularPlural.mpSingular, aContextSingularPlural.mpPlural, n, loc);
244 OUString result(ExpandVariables(OUString::fromUtf8(ret.data())));
246 if (comphelper::LibreOfficeKit::isActive())
248 if (std::use_facet<boost::locale::info>(loc).country() == "CH" &&
249 std::use_facet<boost::locale::info>(loc).language() == "de")
250 result = result.replaceAll(OUString::fromUtf8("\xC3\x9F"), "ss");
252 return result;
255 static ResHookProc pImplResHookProc = nullptr;
257 OUString ExpandVariables(const OUString& rString)
259 if (pImplResHookProc)
260 return pImplResHookProc(rString);
261 return rString;
264 void SetReadStringHook( ResHookProc pProc )
266 pImplResHookProc = pProc;
269 ResHookProc GetReadStringHook()
271 return pImplResHookProc;
275 bool TranslateId::operator==(const TranslateId& other) const
277 if (mpContext == nullptr || other.mpContext == nullptr)
279 if (mpContext != other.mpContext)
280 return false;
282 else if (strcmp(mpContext, other.mpContext) != 0)
283 return false;
285 if (mpId == nullptr || other.mpId == nullptr)
287 return mpId == other.mpId;
289 return strcmp(getId(),other.getId()) == 0;
292 bool TranslateNId::operator==(const TranslateNId& other) const
294 if (mpContext == nullptr || other.mpContext == nullptr)
296 if (mpContext != other.mpContext)
297 return false;
299 else if (strcmp(mpContext, other.mpContext) != 0)
300 return false;
302 if (mpSingular == nullptr || other.mpSingular == nullptr)
304 if (mpSingular != other.mpSingular)
305 return false;
307 else if (strcmp(mpSingular, other.mpSingular) != 0)
308 return false;
310 if (mpPlural == nullptr || other.mpPlural == nullptr)
312 return mpPlural == other.mpPlural;
314 return strcmp(mpPlural,other.mpPlural) == 0;
317 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */