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

source code of boost/libs/locale/include/boost/locale/message.hpp