message.hpp 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550
  1. //
  2. // Copyright (c) 2009-2011 Artyom Beilis (Tonkikh)
  3. // Copyright (c) 2021-2023 Alexander Grund
  4. //
  5. // Distributed under the Boost Software License, Version 1.0.
  6. // https://www.boost.org/LICENSE_1_0.txt
  7. #ifndef BOOST_LOCALE_MESSAGE_HPP_INCLUDED
  8. #define BOOST_LOCALE_MESSAGE_HPP_INCLUDED
  9. #include <boost/locale/detail/facet_id.hpp>
  10. #include <boost/locale/detail/is_supported_char.hpp>
  11. #include <boost/locale/formatting.hpp>
  12. #include <boost/locale/util/string.hpp>
  13. #include <locale>
  14. #include <memory>
  15. #include <set>
  16. #include <string>
  17. #include <type_traits>
  18. #ifdef BOOST_MSVC
  19. # pragma warning(push)
  20. # pragma warning(disable : 4275 4251 4231 4660)
  21. #endif
  22. // glibc < 2.3.4 declares those as macros if compiled with optimization turned on
  23. #ifdef gettext
  24. # undef gettext
  25. # undef ngettext
  26. # undef dgettext
  27. # undef dngettext
  28. #endif
  29. namespace boost { namespace locale {
  30. ///
  31. /// \defgroup message Message Formatting (translation)
  32. ///
  33. /// This module provides message translation functionality, i.e. allow your application to speak native language
  34. ///
  35. /// @{
  36. ///
  37. /// Type used for the count/n argument to the translation functions choosing between singular and plural forms
  38. using count_type = long long;
  39. /// \brief This facet provides message formatting abilities
  40. template<typename CharType>
  41. class BOOST_SYMBOL_VISIBLE message_format : public std::locale::facet,
  42. public detail::facet_id<message_format<CharType>> {
  43. BOOST_LOCALE_ASSERT_IS_SUPPORTED(CharType);
  44. public:
  45. /// Character type
  46. typedef CharType char_type;
  47. /// String type
  48. typedef std::basic_string<CharType> string_type;
  49. /// Standard constructor
  50. message_format(size_t refs = 0) : std::locale::facet(refs) {}
  51. /// This function returns a pointer to the string for a message defined by a \a context
  52. /// and identification string \a id. Both create a single key for message lookup in
  53. /// a domain defined by \a domain_id.
  54. ///
  55. /// If \a context is NULL it is not considered to be a part of the key
  56. ///
  57. /// If a translated string is found, it is returned, otherwise NULL is returned
  58. virtual const char_type* get(int domain_id, const char_type* context, const char_type* id) const = 0;
  59. /// This function returns a pointer to the string for a plural message defined by a \a context
  60. /// and identification string \a single_id.
  61. ///
  62. /// If \a context is NULL it is not considered to be a part of the key
  63. ///
  64. /// Both create a single key for message lookup in
  65. /// a domain defined \a domain_id. \a n is used to pick the correct translation string for a specific
  66. /// number.
  67. ///
  68. /// If a translated string is found, it is returned, otherwise NULL is returned
  69. virtual const char_type*
  70. get(int domain_id, const char_type* context, const char_type* single_id, count_type n) const = 0;
  71. /// Convert a string that defines \a domain to the integer id used by \a get functions
  72. virtual int domain(const std::string& domain) const = 0;
  73. /// Convert the string \a msg to target locale's encoding. If \a msg is already
  74. /// in target encoding it would be returned otherwise the converted
  75. /// string is stored in temporary \a buffer and buffer.c_str() is returned.
  76. ///
  77. /// Note: for char_type that is char16_t, char32_t and wchar_t it is no-op, returns
  78. /// msg
  79. virtual const char_type* convert(const char_type* msg, string_type& buffer) const = 0;
  80. };
  81. /// \cond INTERNAL
  82. namespace detail {
  83. inline bool is_us_ascii_char(char c)
  84. {
  85. // works for null terminated strings regardless char "signedness"
  86. return 0 < c && c < 0x7F;
  87. }
  88. inline bool is_us_ascii_string(const char* msg)
  89. {
  90. while(*msg) {
  91. if(!is_us_ascii_char(*msg++))
  92. return false;
  93. }
  94. return true;
  95. }
  96. template<typename CharType>
  97. struct string_cast_traits {
  98. static const CharType* cast(const CharType* msg, std::basic_string<CharType>& /*unused*/) { return msg; }
  99. };
  100. template<>
  101. struct string_cast_traits<char> {
  102. static const char* cast(const char* msg, std::string& buffer)
  103. {
  104. if(is_us_ascii_string(msg))
  105. return msg;
  106. buffer.reserve(strlen(msg));
  107. char c;
  108. while((c = *msg++) != 0) {
  109. if(is_us_ascii_char(c))
  110. buffer += c;
  111. }
  112. return buffer.c_str();
  113. }
  114. };
  115. } // namespace detail
  116. /// \endcond
  117. /// \brief This class represents a message that can be converted to a specific locale message
  118. ///
  119. /// It holds the original ASCII string that is queried in the dictionary when converting to the output string.
  120. /// The created string may be UTF-8, UTF-16, UTF-32 or other 8-bit encoded string according to the target
  121. /// character type and locale encoding.
  122. template<typename CharType>
  123. class basic_message {
  124. public:
  125. typedef CharType char_type; ///< The character this message object is used with
  126. typedef std::basic_string<char_type> string_type; ///< The string type this object can be used with
  127. typedef message_format<char_type> facet_type; ///< The type of the facet the messages are fetched with
  128. /// Create default empty message
  129. basic_message() : n_(0), c_id_(nullptr), c_context_(nullptr), c_plural_(nullptr) {}
  130. /// Create a simple message from 0 terminated string. The string should exist
  131. /// until the message is destroyed. Generally useful with static constant strings
  132. explicit basic_message(const char_type* id) : n_(0), c_id_(id), c_context_(nullptr), c_plural_(nullptr) {}
  133. /// Create a simple plural form message from 0 terminated strings. The strings should exist
  134. /// until the message is destroyed. Generally useful with static constant strings.
  135. ///
  136. /// \a n is the number, \a single and \a plural are singular and plural forms of the message
  137. explicit basic_message(const char_type* single, const char_type* plural, count_type n) :
  138. n_(n), c_id_(single), c_context_(nullptr), c_plural_(plural)
  139. {}
  140. /// Create a simple message from 0 terminated strings, with context
  141. /// information. The string should exist
  142. /// until the message is destroyed. Generally useful with static constant strings
  143. explicit basic_message(const char_type* context, const char_type* id) :
  144. n_(0), c_id_(id), c_context_(context), c_plural_(nullptr)
  145. {}
  146. /// Create a simple plural form message from 0 terminated strings, with context. The strings should exist
  147. /// until the message is destroyed. Generally useful with static constant strings.
  148. ///
  149. /// \a n is the number, \a single and \a plural are singular and plural forms of the message
  150. explicit basic_message(const char_type* context,
  151. const char_type* single,
  152. const char_type* plural,
  153. count_type n) :
  154. n_(n),
  155. c_id_(single), c_context_(context), c_plural_(plural)
  156. {}
  157. /// Create a simple message from a string.
  158. explicit basic_message(const string_type& id) :
  159. n_(0), c_id_(nullptr), c_context_(nullptr), c_plural_(nullptr), id_(id)
  160. {}
  161. /// Create a simple plural form message from strings.
  162. ///
  163. /// \a n is the number, \a single and \a plural are single and plural forms of the message
  164. explicit basic_message(const string_type& single, const string_type& plural, count_type number) :
  165. n_(number), c_id_(nullptr), c_context_(nullptr), c_plural_(nullptr), id_(single), plural_(plural)
  166. {}
  167. /// Create a simple message from a string with context.
  168. explicit basic_message(const string_type& context, const string_type& id) :
  169. n_(0), c_id_(nullptr), c_context_(nullptr), c_plural_(nullptr), id_(id), context_(context)
  170. {}
  171. /// Create a simple plural form message from strings.
  172. ///
  173. /// \a n is the number, \a single and \a plural are single and plural forms of the message
  174. explicit basic_message(const string_type& context,
  175. const string_type& single,
  176. const string_type& plural,
  177. count_type number) :
  178. n_(number),
  179. c_id_(nullptr), c_context_(nullptr), c_plural_(nullptr), id_(single), context_(context), plural_(plural)
  180. {}
  181. /// Copy an object
  182. basic_message(const basic_message&) = default;
  183. basic_message(basic_message&&) noexcept = default;
  184. /// Assign other message object to this one
  185. basic_message& operator=(const basic_message&) = default;
  186. basic_message&
  187. operator=(basic_message&&) noexcept(std::is_nothrow_move_assignable<string_type>::value) = default;
  188. /// Swap two message objects
  189. void
  190. swap(basic_message& other) noexcept(noexcept(std::declval<string_type&>().swap(std::declval<string_type&>())))
  191. {
  192. using std::swap;
  193. swap(n_, other.n_);
  194. swap(c_id_, other.c_id_);
  195. swap(c_context_, other.c_context_);
  196. swap(c_plural_, other.c_plural_);
  197. swap(id_, other.id_);
  198. swap(context_, other.context_);
  199. swap(plural_, other.plural_);
  200. }
  201. friend void swap(basic_message& x, basic_message& y) noexcept(noexcept(x.swap(y))) { x.swap(y); }
  202. /// Message class can be explicitly converted to string class
  203. operator string_type() const { return str(); }
  204. /// Translate message to a string in the default global locale, using default domain
  205. string_type str() const { return str(std::locale()); }
  206. /// Translate message to a string in the locale \a locale, using default domain
  207. string_type str(const std::locale& locale) const { return str(locale, 0); }
  208. /// Translate message to a string using locale \a locale and message domain \a domain_id
  209. string_type str(const std::locale& locale, const std::string& domain_id) const
  210. {
  211. int id = 0;
  212. if(std::has_facet<facet_type>(locale))
  213. id = std::use_facet<facet_type>(locale).domain(domain_id);
  214. return str(locale, id);
  215. }
  216. /// Translate message to a string using the default locale and message domain \a domain_id
  217. string_type str(const std::string& domain_id) const { return str(std::locale(), domain_id); }
  218. /// Translate message to a string using locale \a loc and message domain index \a id
  219. string_type str(const std::locale& loc, int id) const
  220. {
  221. string_type buffer;
  222. const char_type* ptr = write(loc, id, buffer);
  223. if(ptr != buffer.c_str())
  224. buffer = ptr;
  225. return buffer;
  226. }
  227. /// Translate message and write to stream \a out, using imbued locale and domain set to the
  228. /// stream
  229. void write(std::basic_ostream<char_type>& out) const
  230. {
  231. const std::locale& loc = out.getloc();
  232. int id = ios_info::get(out).domain_id();
  233. string_type buffer;
  234. out << write(loc, id, buffer);
  235. }
  236. private:
  237. const char_type* plural() const
  238. {
  239. if(c_plural_)
  240. return c_plural_;
  241. if(plural_.empty())
  242. return nullptr;
  243. return plural_.c_str();
  244. }
  245. const char_type* context() const
  246. {
  247. if(c_context_)
  248. return c_context_;
  249. if(context_.empty())
  250. return nullptr;
  251. return context_.c_str();
  252. }
  253. const char_type* id() const { return c_id_ ? c_id_ : id_.c_str(); }
  254. const char_type* write(const std::locale& loc, int domain_id, string_type& buffer) const
  255. {
  256. static const char_type empty_string[1] = {0};
  257. const char_type* id = this->id();
  258. const char_type* context = this->context();
  259. const char_type* plural = this->plural();
  260. if(*id == 0)
  261. return empty_string;
  262. const facet_type* facet = nullptr;
  263. if(std::has_facet<facet_type>(loc))
  264. facet = &std::use_facet<facet_type>(loc);
  265. const char_type* translated = nullptr;
  266. if(facet) {
  267. if(!plural)
  268. translated = facet->get(domain_id, context, id);
  269. else
  270. translated = facet->get(domain_id, context, id, n_);
  271. }
  272. if(!translated) {
  273. const char_type* msg = plural ? (n_ == 1 ? id : plural) : id;
  274. if(facet)
  275. translated = facet->convert(msg, buffer);
  276. else
  277. translated = detail::string_cast_traits<char_type>::cast(msg, buffer);
  278. }
  279. return translated;
  280. }
  281. /// members
  282. count_type n_;
  283. const char_type* c_id_;
  284. const char_type* c_context_;
  285. const char_type* c_plural_;
  286. string_type id_;
  287. string_type context_;
  288. string_type plural_;
  289. };
  290. /// Convenience typedef for char
  291. typedef basic_message<char> message;
  292. /// Convenience typedef for wchar_t
  293. typedef basic_message<wchar_t> wmessage;
  294. #ifndef BOOST_LOCALE_NO_CXX20_STRING8
  295. /// Convenience typedef for char8_t
  296. typedef basic_message<char8_t> u8message;
  297. #endif
  298. #ifdef BOOST_LOCALE_ENABLE_CHAR16_T
  299. /// Convenience typedef for char16_t
  300. typedef basic_message<char16_t> u16message;
  301. #endif
  302. #ifdef BOOST_LOCALE_ENABLE_CHAR32_T
  303. /// Convenience typedef for char32_t
  304. typedef basic_message<char32_t> u32message;
  305. #endif
  306. /// Translate message \a msg and write it to stream
  307. template<typename CharType>
  308. std::basic_ostream<CharType>& operator<<(std::basic_ostream<CharType>& out, const basic_message<CharType>& msg)
  309. {
  310. msg.write(out);
  311. return out;
  312. }
  313. /// \anchor boost_locale_translate_family \name Indirect message translation function family
  314. /// @{
  315. /// \brief Translate a message, \a msg is not copied
  316. template<typename CharType>
  317. inline basic_message<CharType> translate(const CharType* msg)
  318. {
  319. return basic_message<CharType>(msg);
  320. }
  321. /// \brief Translate a message in context, \a msg and \a context are not copied
  322. template<typename CharType>
  323. inline basic_message<CharType> translate(const CharType* context, const CharType* msg)
  324. {
  325. return basic_message<CharType>(context, msg);
  326. }
  327. /// \brief Translate a plural message form, \a single and \a plural are not copied
  328. template<typename CharType>
  329. inline basic_message<CharType> translate(const CharType* single, const CharType* plural, count_type n)
  330. {
  331. return basic_message<CharType>(single, plural, n);
  332. }
  333. /// \brief Translate a plural message from in context, \a context, \a single and \a plural are not copied
  334. template<typename CharType>
  335. inline basic_message<CharType>
  336. translate(const CharType* context, const CharType* single, const CharType* plural, count_type n)
  337. {
  338. return basic_message<CharType>(context, single, plural, n);
  339. }
  340. /// \brief Translate a message, \a msg is copied
  341. template<typename CharType>
  342. inline basic_message<CharType> translate(const std::basic_string<CharType>& msg)
  343. {
  344. return basic_message<CharType>(msg);
  345. }
  346. /// \brief Translate a message in context,\a context and \a msg is copied
  347. template<typename CharType>
  348. inline basic_message<CharType> translate(const std::basic_string<CharType>& context,
  349. const std::basic_string<CharType>& msg)
  350. {
  351. return basic_message<CharType>(context, msg);
  352. }
  353. /// \brief Translate a plural message form in context, \a context, \a single and \a plural are copied
  354. template<typename CharType>
  355. inline basic_message<CharType> translate(const std::basic_string<CharType>& context,
  356. const std::basic_string<CharType>& single,
  357. const std::basic_string<CharType>& plural,
  358. count_type n)
  359. {
  360. return basic_message<CharType>(context, single, plural, n);
  361. }
  362. /// \brief Translate a plural message form, \a single and \a plural are copied
  363. template<typename CharType>
  364. inline basic_message<CharType>
  365. translate(const std::basic_string<CharType>& single, const std::basic_string<CharType>& plural, count_type n)
  366. {
  367. return basic_message<CharType>(single, plural, n);
  368. }
  369. /// @}
  370. /// \anchor boost_locale_gettext_family \name Direct message translation functions family
  371. /// Translate message \a id according to locale \a loc
  372. template<typename CharType>
  373. std::basic_string<CharType> gettext(const CharType* id, const std::locale& loc = std::locale())
  374. {
  375. return basic_message<CharType>(id).str(loc);
  376. }
  377. /// Translate plural form according to locale \a loc
  378. template<typename CharType>
  379. std::basic_string<CharType>
  380. ngettext(const CharType* s, const CharType* p, count_type n, const std::locale& loc = std::locale())
  381. {
  382. return basic_message<CharType>(s, p, n).str(loc);
  383. }
  384. /// Translate message \a id according to locale \a loc in domain \a domain
  385. template<typename CharType>
  386. std::basic_string<CharType> dgettext(const char* domain, const CharType* id, const std::locale& loc = std::locale())
  387. {
  388. return basic_message<CharType>(id).str(loc, domain);
  389. }
  390. /// Translate plural form according to locale \a loc in domain \a domain
  391. template<typename CharType>
  392. std::basic_string<CharType> dngettext(const char* domain,
  393. const CharType* s,
  394. const CharType* p,
  395. count_type n,
  396. const std::locale& loc = std::locale())
  397. {
  398. return basic_message<CharType>(s, p, n).str(loc, domain);
  399. }
  400. /// Translate message \a id according to locale \a loc in context \a context
  401. template<typename CharType>
  402. std::basic_string<CharType>
  403. pgettext(const CharType* context, const CharType* id, const std::locale& loc = std::locale())
  404. {
  405. return basic_message<CharType>(context, id).str(loc);
  406. }
  407. /// Translate plural form according to locale \a loc in context \a context
  408. template<typename CharType>
  409. std::basic_string<CharType> npgettext(const CharType* context,
  410. const CharType* s,
  411. const CharType* p,
  412. count_type n,
  413. const std::locale& loc = std::locale())
  414. {
  415. return basic_message<CharType>(context, s, p, n).str(loc);
  416. }
  417. /// Translate message \a id according to locale \a loc in domain \a domain in context \a context
  418. template<typename CharType>
  419. std::basic_string<CharType>
  420. dpgettext(const char* domain, const CharType* context, const CharType* id, const std::locale& loc = std::locale())
  421. {
  422. return basic_message<CharType>(context, id).str(loc, domain);
  423. }
  424. /// Translate plural form according to locale \a loc in domain \a domain in context \a context
  425. template<typename CharType>
  426. std::basic_string<CharType> dnpgettext(const char* domain,
  427. const CharType* context,
  428. const CharType* s,
  429. const CharType* p,
  430. count_type n,
  431. const std::locale& loc = std::locale())
  432. {
  433. return basic_message<CharType>(context, s, p, n).str(loc, domain);
  434. }
  435. /// @}
  436. namespace as {
  437. /// \cond INTERNAL
  438. namespace detail {
  439. struct set_domain {
  440. std::string domain_id;
  441. };
  442. template<typename CharType>
  443. std::basic_ostream<CharType>& operator<<(std::basic_ostream<CharType>& out, const set_domain& dom)
  444. {
  445. int id = std::use_facet<message_format<CharType>>(out.getloc()).domain(dom.domain_id);
  446. ios_info::get(out).domain_id(id);
  447. return out;
  448. }
  449. } // namespace detail
  450. /// \endcond
  451. /// \addtogroup manipulators
  452. ///
  453. /// @{
  454. /// Manipulator for switching message domain in ostream,
  455. ///
  456. /// \note The returned object throws std::bad_cast if the I/O stream does not have \ref message_format facet
  457. /// installed
  458. inline
  459. #ifdef BOOST_LOCALE_DOXYGEN
  460. unspecified_type
  461. #else
  462. detail::set_domain
  463. #endif
  464. domain(const std::string& id)
  465. {
  466. detail::set_domain tmp = {id};
  467. return tmp;
  468. }
  469. /// @}
  470. } // namespace as
  471. }} // namespace boost::locale
  472. #ifdef BOOST_MSVC
  473. # pragma warning(pop)
  474. #endif
  475. #endif