diff options
Diffstat (limited to 'unotools/source/i18n/resmgr.cxx')
-rw-r--r-- | unotools/source/i18n/resmgr.cxx | 312 |
1 files changed, 312 insertions, 0 deletions
diff --git a/unotools/source/i18n/resmgr.cxx b/unotools/source/i18n/resmgr.cxx new file mode 100644 index 000000000..641fa61be --- /dev/null +++ b/unotools/source/i18n/resmgr.cxx @@ -0,0 +1,312 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <boost/version.hpp> +#if BOOST_VERSION < 106700 +// Needed when #include <boost/locale.hpp> below includes Boost 1.65.1 +// workdir/UnpackedTarball/boost/boost/locale/format.hpp using "std::auto_ptr<data> d;", but must +// come very early here in case <memory> is already (indirectly) included earlier: +#include <config_libcxx.h> +#if HAVE_LIBCPP +#define _LIBCPP_ENABLE_CXX17_REMOVED_AUTO_PTR +#elif defined _MSC_VER +#define _HAS_AUTO_PTR_ETC 1 +#endif +#endif + +#include <sal/config.h> + +#include <cassert> + +#include <string.h> +#include <stdio.h> +#if defined UNX && !defined MACOSX && !defined IOS && !defined ANDROID +# include <libintl.h> +#endif + +#include <comphelper/lok.hxx> +#include <unotools/resmgr.hxx> +#include <osl/thread.h> +#include <osl/file.hxx> +#include <rtl/crc.h> +#include <rtl/bootstrap.hxx> +#include <i18nlangtag/languagetag.hxx> + +#include <boost/locale.hpp> +#include <boost/locale/gnu_gettext.hpp> + +#include <unordered_map> + +#ifdef ANDROID +#include <osl/detail/android-bootstrap.h> +#endif + +#if defined(_WIN32) && defined(DBG_UTIL) +#include <o3tl/char16_t2wchar_t.hxx> +#include <prewin.h> +#include <crtdbg.h> +#include <postwin.h> +#endif + +namespace +{ + OUString createFromUtf8(const char* data, size_t size) + { + OUString aTarget; + bool bSuccess = rtl_convertStringToUString(&aTarget.pData, + data, + size, + RTL_TEXTENCODING_UTF8, + RTL_TEXTTOUNICODE_FLAGS_UNDEFINED_ERROR|RTL_TEXTTOUNICODE_FLAGS_MBUNDEFINED_ERROR|RTL_TEXTTOUNICODE_FLAGS_INVALID_ERROR); + (void) bSuccess; + assert(bSuccess); + return aTarget; + } + + OString genKeyId(const OString& rGenerator) + { + sal_uInt32 nCRC = rtl_crc32(0, rGenerator.getStr(), rGenerator.getLength()); + // Use simple ASCII characters, exclude I, l, 1 and O, 0 to avoid confusing IDs + static const char sSymbols[] = + "ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz23456789"; + char sKeyId[6]; + for (short nKeyInd = 0; nKeyInd < 5; ++nKeyInd) + { + sKeyId[nKeyInd] = sSymbols[(nCRC & 63) % strlen(sSymbols)]; + nCRC >>= 6; + } + sKeyId[5] = '\0'; + return OString(sKeyId); + } +} + +#if defined(_WIN32) && defined(DBG_UTIL) +static int IgnoringCrtReportHook(int reportType, wchar_t *message, int * /* returnValue */) +{ + OUString sType; + if (reportType == _CRT_WARN) + sType = "WARN"; + else if (reportType == _CRT_ERROR) + sType = "ERROR"; + else if (reportType == _CRT_ASSERT) + sType = "ASSERT"; + else + sType = "?(" + OUString::number(reportType) + ")"; + + SAL_WARN("unotools.i18n", "CRT Report Hook: " << sType << ": " << OUString(o3tl::toU(message))); + + return TRUE; +} +#endif + + +namespace Translate +{ + std::locale Create(std::string_view aPrefixName, const LanguageTag& rLocale) + { + static std::unordered_map<OString, std::locale> aCache; + OString sIdentifier = rLocale.getGlibcLocaleString(u".UTF-8").toUtf8(); + OString sUnique = sIdentifier + aPrefixName; + auto aFind = aCache.find(sUnique); + if (aFind != aCache.end()) + return aFind->second; + boost::locale::generator gen; + gen.characters(boost::locale::char_facet); + gen.categories(boost::locale::message_facet | boost::locale::information_facet); +#if defined(ANDROID) + OString sPath(OString(lo_get_app_data_dir()) + "/program/resource"); +#else + OUString uri("$BRAND_BASE_DIR/$BRAND_SHARE_RESOURCE_SUBDIR/"); + rtl::Bootstrap::expandMacros(uri); + OUString path; + osl::File::getSystemPathFromFileURL(uri, path); +#if defined _WIN32 + // add_messages_path is documented to treat path string in the *created* locale's encoding + // on Windows; creating an UTF-8 encoding, we're lucky to have Unicode path support here. + constexpr rtl_TextEncoding eEncoding = RTL_TEXTENCODING_UTF8; +#else + const rtl_TextEncoding eEncoding = osl_getThreadTextEncoding(); +#endif + OString sPath(OUStringToOString(path, eEncoding)); +#endif + gen.add_messages_path(sPath.getStr()); +#if defined UNX && !defined MACOSX && !defined IOS && !defined ANDROID + // allow gettext to find these .mo files e.g. so gtk dialogs can use them + bindtextdomain(aPrefixName.data(), sPath.getStr()); + // tdf#131069 gtk, and anything sane, always wants utf-8 strings as output + bind_textdomain_codeset(aPrefixName.data(), "UTF-8"); +#endif + gen.add_messages_domain(aPrefixName.data()); + +#if defined(_WIN32) && defined(DBG_UTIL) + // With a newer C++ debug runtime (in an --enable-dbgutil build), passing an invalid locale + // name causes an attempt to display an error dialog. Which does not even show up, at least + // for me, but instead the process (gengal, at least) just hangs. Which is far from ideal. + + // Passing a POSIX-style locale name to the std::locale constructor on Windows is a bit odd, + // but apparently in the normal C++ runtime it "just" causes an exception to be thrown, that + // boost catches (see the loadable(std::string name) in boost's + // libs\locale\src\std\std_backend.cpp), and then instead uses the Windows style locale name + // it knows how to construct. (Why does it even try the POSIX style name I can't + // understand.) + + // Actually it isn't just the locale name part "en_US" of a locale like "en_US.UTF-8" that + // is problematic, but also the encoding part, "UTF-8". The Microsoft C/C++ library does not + // support UTF-8 locales. The error message that our own report hook catches says: + // "f:\dd\vctools\crt\crtw32\stdcpp\xmbtowc.c(89) : Assertion failed: ploc->_Mbcurmax == 1 + // || ploc->_Mbcurmax == 2". Clearly in a UTF-8 locale (perhaps one that boost internally + // constructs?) the maximum bytes per character will be more than 2. + + // With a debug C++ runtime, we need to avoid the error dialog, and just ignore the error. + + struct CrtSetReportHook + { + int mnCrtSetReportHookSucceeded; + + CrtSetReportHook() + { + mnCrtSetReportHookSucceeded = _CrtSetReportHookW2(_CRT_RPTHOOK_INSTALL, IgnoringCrtReportHook); + } + + ~CrtSetReportHook() + { + if (mnCrtSetReportHookSucceeded >= 0) + _CrtSetReportHookW2(_CRT_RPTHOOK_REMOVE, IgnoringCrtReportHook); + } + } aHook; + +#endif + + std::locale aRet(gen(sIdentifier.getStr())); + + aCache[sUnique] = aRet; + return aRet; + } + + OUString get(TranslateId sContextAndId, const std::locale &loc) + { + assert(!strchr(sContextAndId.mpId, '\004') && "should be using nget, not get"); + + //if it's a key id locale, generate it here + if (std::use_facet<boost::locale::info>(loc).language() == "qtz") + { + OString sKeyId(genKeyId(OString::Concat(sContextAndId.mpContext) + "|" + std::string_view(sContextAndId.mpId))); + return OUString::fromUtf8(sKeyId) + u"\u2016" + createFromUtf8(sContextAndId.mpId, strlen(sContextAndId.mpId)); + } + + //otherwise translate it + const std::string ret = boost::locale::pgettext(sContextAndId.mpContext, sContextAndId.mpId, loc); + OUString result(ExpandVariables(createFromUtf8(ret.data(), ret.size()))); + + if (comphelper::LibreOfficeKit::isActive()) + { + // If it is de-CH, change sharp s to double s. + if (std::use_facet<boost::locale::info>(loc).country() == "CH" && + std::use_facet<boost::locale::info>(loc).language() == "de") + result = result.replaceAll(OUString::fromUtf8("\xC3\x9F"), "ss"); + } + return result; + } + + OUString nget(TranslateNId aContextSingularPlural, int n, const std::locale &loc) + { + //if it's a key id locale, generate it here + if (std::use_facet<boost::locale::info>(loc).language() == "qtz") + { + OString sKeyId(genKeyId(OString::Concat(aContextSingularPlural.mpContext) + "|" + aContextSingularPlural.mpSingular)); + const char* pForm = n == 0 ? aContextSingularPlural.mpSingular : aContextSingularPlural.mpPlural; + return OUString::fromUtf8(sKeyId) + u"\u2016" + createFromUtf8(pForm, strlen(pForm)); + } + + //otherwise translate it + const std::string ret = boost::locale::npgettext(aContextSingularPlural.mpContext, aContextSingularPlural.mpSingular, aContextSingularPlural.mpPlural, n, loc); + OUString result(ExpandVariables(createFromUtf8(ret.data(), ret.size()))); + + if (comphelper::LibreOfficeKit::isActive()) + { + if (std::use_facet<boost::locale::info>(loc).country() == "CH" && + std::use_facet<boost::locale::info>(loc).language() == "de") + result = result.replaceAll(OUString::fromUtf8("\xC3\x9F"), "ss"); + } + return result; + } + + static ResHookProc pImplResHookProc = nullptr; + + OUString ExpandVariables(const OUString& rString) + { + if (pImplResHookProc) + return pImplResHookProc(rString); + return rString; + } + + void SetReadStringHook( ResHookProc pProc ) + { + pImplResHookProc = pProc; + } + + ResHookProc GetReadStringHook() + { + return pImplResHookProc; + } +} + +bool TranslateId::operator==(const TranslateId& other) const +{ + if (mpContext == nullptr || other.mpContext == nullptr) + { + if (mpContext != other.mpContext) + return false; + } + else if (strcmp(mpContext, other.mpContext) != 0) + return false; + + if (mpId == nullptr || other.mpId == nullptr) + { + return mpId == other.mpId; + } + return strcmp(mpId,other.mpId) == 0; +} + +bool TranslateNId::operator==(const TranslateNId& other) const +{ + if (mpContext == nullptr || other.mpContext == nullptr) + { + if (mpContext != other.mpContext) + return false; + } + else if (strcmp(mpContext, other.mpContext) != 0) + return false; + + if (mpSingular == nullptr || other.mpSingular == nullptr) + { + if (mpSingular != other.mpSingular) + return false; + } + else if (strcmp(mpSingular, other.mpSingular) != 0) + return false; + + if (mpPlural == nullptr || other.mpPlural == nullptr) + { + return mpPlural == other.mpPlural; + } + return strcmp(mpPlural,other.mpPlural) == 0; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |