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 | #include <boost/locale/encoding.hpp> |
9 | #include <boost/locale/generator.hpp> |
10 | #include <boost/locale/gnu_gettext.hpp> |
11 | #include <boost/locale/localization_backend.hpp> |
12 | #include <boost/locale/message.hpp> |
13 | #include "boostLocale/test/tools.hpp" |
14 | #include "boostLocale/test/unit_test.hpp" |
15 | #include <fstream> |
16 | #include <iostream> |
17 | #include <limits> |
18 | #include <type_traits> |
19 | #include <vector> |
20 | |
21 | namespace bl = boost::locale; |
22 | |
23 | void test_messages_info() |
24 | { |
25 | using string_vec = std::vector<std::string>; |
26 | { |
27 | bl::gnu_gettext::messages_info info; |
28 | info.locale_category = "LC" ; |
29 | TEST_EQ(info.get_catalog_paths(), string_vec{}); |
30 | info.paths.push_back(x: "." ); |
31 | TEST_EQ(info.get_catalog_paths(), string_vec{"./C/LC" }); |
32 | info.language = "en" ; |
33 | TEST_EQ(info.get_catalog_paths(), string_vec{"./en/LC" }); |
34 | info.country = "US" ; |
35 | TEST_EQ(info.get_catalog_paths(), (string_vec{"./en_US/LC" , "./en/LC" })); |
36 | info.country.clear(); |
37 | info.variant = "euro" ; |
38 | TEST_EQ(info.get_catalog_paths(), (string_vec{"./en@euro/LC" , "./en/LC" })); |
39 | info.country = "US" ; |
40 | TEST_EQ(info.get_catalog_paths(), (string_vec{"./en_US@euro/LC" , "./en@euro/LC" , "./en_US/LC" , "./en/LC" })); |
41 | |
42 | info.paths = string_vec{"/1" , "/2" }; |
43 | TEST_EQ(info.get_catalog_paths(), |
44 | (string_vec{"/1/en_US@euro/LC" , |
45 | "/2/en_US@euro/LC" , |
46 | "/1/en@euro/LC" , |
47 | "/2/en@euro/LC" , |
48 | "/1/en_US/LC" , |
49 | "/2/en_US/LC" , |
50 | "/1/en/LC" , |
51 | "/2/en/LC" })); |
52 | } |
53 | } |
54 | |
55 | std::string backend; |
56 | bool file_loader_is_actually_called = false; |
57 | |
58 | struct file_loader { |
59 | std::vector<char> operator()(const std::string& name, const std::string& /*encoding*/) const |
60 | { |
61 | std::vector<char> buffer; |
62 | std::ifstream f(name.c_str(), std::ifstream::binary); |
63 | if(!f) |
64 | return buffer; |
65 | f.seekg(0, std::ifstream::end); |
66 | const auto len = f.tellg(); |
67 | f.seekg(0); |
68 | if(len > 0) { |
69 | buffer.resize(new_size: static_cast<size_t>(len)); |
70 | f.read(s: buffer.data(), n: buffer.size()); |
71 | } |
72 | file_loader_is_actually_called = true; |
73 | return buffer; |
74 | } |
75 | }; |
76 | |
77 | std::string same_s(std::string s) |
78 | { |
79 | return s; |
80 | } |
81 | |
82 | std::wstring same_w(std::wstring s) |
83 | { |
84 | return s; |
85 | } |
86 | |
87 | #ifndef BOOST_LOCALE_NO_CXX20_STRING8 |
88 | std::basic_string<char8_t> same_u8(std::basic_string<char8_t> s) |
89 | { |
90 | return s; |
91 | } |
92 | #endif |
93 | |
94 | #ifdef BOOST_LOCALE_ENABLE_CHAR16_T |
95 | std::u16string same_u16(std::u16string s) |
96 | { |
97 | return s; |
98 | } |
99 | #endif |
100 | |
101 | #ifdef BOOST_LOCALE_ENABLE_CHAR32_T |
102 | std::u32string same_u32(std::u32string s) |
103 | { |
104 | return s; |
105 | } |
106 | #endif |
107 | |
108 | namespace impl { |
109 | |
110 | template<class T, std::size_t = sizeof(T)> |
111 | std::true_type is_complete_impl(T*); |
112 | std::false_type is_complete_impl(...); |
113 | |
114 | template<class T> |
115 | using has_ctype = decltype(is_complete_impl(std::declval<std::ctype<T>*>())); |
116 | |
117 | template<typename Char, typename... Ts> |
118 | typename std::enable_if<has_ctype<Char>::value, bool>::type stream_translate(std::basic_ostream<Char>& ss, Ts&&... args) |
119 | { |
120 | ss << bl::translate(args...); |
121 | return true; |
122 | } |
123 | |
124 | // Required for char types not fully supported by the standard library |
125 | // e.g.: error: implicit instantiation of undefined template 'std::ctype<char8_t>' |
126 | template<typename Char, typename... Ts> |
127 | typename std::enable_if<!has_ctype<Char>::value, bool>::type stream_translate(std::basic_ostream<Char>&, Ts&&...) |
128 | { |
129 | return false; // LCOV_EXCL_LINE |
130 | } |
131 | |
132 | template<typename Char> |
133 | void test_cntranslate(const std::string& sContext, |
134 | const std::string& sSingular, |
135 | const std::string& sPlural, |
136 | long long n, |
137 | const std::string& sExpected, |
138 | const std::locale& l, |
139 | const std::string& domain) |
140 | { |
141 | typedef std::basic_string<Char> string_type; |
142 | const string_type expected = to_correct_string<Char>(sExpected, l); |
143 | |
144 | const string_type c = to<Char>(sContext); |
145 | const string_type s = to<Char>(sSingular); |
146 | const string_type p = to<Char>(sPlural); |
147 | |
148 | if(domain == "default" ) { |
149 | TEST_EQ(bl::translate(c, s, p, n).str(l), expected); |
150 | TEST_EQ(bl::translate(c.c_str(), s.c_str(), p.c_str(), n).str(l), expected); |
151 | std::locale tmp_locale; |
152 | std::locale::global(loc: l); |
153 | string_type tmp = bl::translate(c, s, p, n); |
154 | TEST_EQ(tmp, expected); |
155 | tmp = bl::translate(c, s, p, n).str(); |
156 | TEST_EQ(tmp, expected); |
157 | std::locale::global(loc: tmp_locale); |
158 | |
159 | std::basic_ostringstream<Char> ss; |
160 | ss.imbue(l); |
161 | if(stream_translate(ss, c, s, p, n)) |
162 | TEST_EQ(ss.str(), expected); |
163 | |
164 | // Copyable & movable |
165 | const string_type s2 = ascii_to<Char>("missing Singular" ); |
166 | const string_type p2 = ascii_to<Char>("missing Plural" ); |
167 | const string_type& expected2 = (n == 1) ? s2 : p2; |
168 | auto translation1 = bl::translate(c, s, p, n); |
169 | auto translation2 = bl::translate(c, s2, p2, n); |
170 | TEST_EQ(translation1.str(l), expected); |
171 | TEST_EQ(translation2.str(l), expected2); |
172 | // Copy |
173 | translation1 = translation2; |
174 | TEST_EQ(translation1.str(l), expected2); |
175 | { |
176 | bl::basic_message<Char> t3(translation2); |
177 | TEST_EQ(t3.str(l), expected2); |
178 | } |
179 | // Move |
180 | translation1 = bl::translate(c, s, p, n); |
181 | translation2 = std::move(translation1); |
182 | TEST_EQ(translation2.str(l), expected); |
183 | { |
184 | bl::basic_message<Char> t3(std::move(translation2)); |
185 | TEST_EQ(t3.str(l), expected); |
186 | } |
187 | // Swap |
188 | translation1 = bl::translate(c, s, p, n); |
189 | translation2 = bl::translate(c, s2, p2, n); |
190 | TEST_EQ(translation1.str(l), expected); |
191 | TEST_EQ(translation2.str(l), expected2); |
192 | using std::swap; |
193 | swap(translation1, translation2); |
194 | TEST_EQ(translation1.str(l), expected2); |
195 | TEST_EQ(translation2.str(l), expected); |
196 | translation1 = bl::translate(c, s, p, n); |
197 | translation2 = bl::translate(c, s2, p2, 1); // n==1! |
198 | swap(translation1, translation2); |
199 | TEST_EQ(translation1.str(l), s2); |
200 | TEST_EQ(translation2.str(l), expected); |
201 | } |
202 | TEST_EQ(bl::translate(c, s, p, n).str(l, domain), expected); |
203 | std::locale tmp_locale; |
204 | std::locale::global(loc: l); |
205 | TEST_EQ(bl::translate(c, s, p, n).str(domain), expected); |
206 | std::locale::global(loc: tmp_locale); |
207 | { |
208 | std::basic_ostringstream<Char> ss; |
209 | ss.imbue(l); |
210 | if(stream_translate(ss << bl::as::domain(id: domain), c, s, p, n)) |
211 | TEST_EQ(ss.str(), expected); |
212 | } |
213 | { |
214 | std::basic_ostringstream<Char> ss; |
215 | ss.imbue(l); |
216 | if(stream_translate(ss << bl::as::domain(id: domain), c.c_str(), s.c_str(), p.c_str(), n)) |
217 | TEST_EQ(ss.str(), expected); |
218 | } |
219 | // Missing facet -> No translation |
220 | { |
221 | const string_type nonAscii = ((string_type() + Char('\x7F')) + Char('\x82')) + Char('\xF0'); |
222 | const string_type p2 = nonAscii + p + nonAscii; |
223 | // For char the non-ASCII chars are removed -> original p |
224 | const string_type expected2 = (n == 1) ? s : (std::is_same<Char, char>::value ? p : p2); |
225 | TEST_EQ(bl::translate(c, s, p2, n).str(std::locale::classic()), expected2); |
226 | } |
227 | } |
228 | |
229 | template<typename Char> |
230 | void test_ntranslate(const std::string& sSingular, |
231 | const std::string& sPlural, |
232 | long long n, |
233 | const std::string& sExpected, |
234 | const std::locale& l, |
235 | const std::string& domain) |
236 | { |
237 | typedef std::basic_string<Char> string_type; |
238 | const string_type expected = to_correct_string<Char>(sExpected, l); |
239 | const string_type s = to<Char>(sSingular); |
240 | const string_type p = to<Char>(sPlural); |
241 | if(domain == "default" ) { |
242 | TEST_EQ(bl::translate(s, p, n).str(l), expected); |
243 | TEST_EQ(bl::translate(s.c_str(), p.c_str(), n).str(l), expected); |
244 | std::locale tmp_locale; |
245 | std::locale::global(loc: l); |
246 | string_type tmp = bl::translate(s, p, n); |
247 | TEST_EQ(tmp, expected); |
248 | tmp = bl::translate(s, p, n).str(); |
249 | TEST_EQ(tmp, expected); |
250 | std::locale::global(loc: tmp_locale); |
251 | |
252 | std::basic_ostringstream<Char> ss; |
253 | ss.imbue(l); |
254 | if(stream_translate(ss, s, p, n)) |
255 | TEST_EQ(ss.str(), expected); |
256 | } |
257 | TEST_EQ(bl::translate(s, p, n).str(l, domain), expected); |
258 | std::locale tmp_locale; |
259 | std::locale::global(loc: l); |
260 | TEST_EQ(bl::translate(s, p, n).str(domain), expected); |
261 | std::locale::global(loc: tmp_locale); |
262 | { |
263 | std::basic_ostringstream<Char> ss; |
264 | ss.imbue(l); |
265 | if(stream_translate(ss << bl::as::domain(id: domain), s, p, n)) |
266 | TEST_EQ(ss.str(), expected); |
267 | } |
268 | { |
269 | std::basic_ostringstream<Char> ss; |
270 | ss.imbue(l); |
271 | if(stream_translate(ss << bl::as::domain(id: domain), s.c_str(), p.c_str(), n)) |
272 | TEST_EQ(ss.str(), expected); |
273 | } |
274 | } |
275 | |
276 | template<typename Char> |
277 | void test_ctranslate(const std::string& sContext, |
278 | const std::string& sOriginal, |
279 | const std::string& sExpected, |
280 | const std::locale& l, |
281 | const std::string& domain) |
282 | { |
283 | typedef std::basic_string<Char> string_type; |
284 | const string_type expected = to_correct_string<Char>(sExpected, l); |
285 | const string_type original = to<Char>(sOriginal); |
286 | const string_type c = to<Char>(sContext); |
287 | if(domain == "default" ) { |
288 | TEST_EQ(bl::translate(c, original).str(l), expected); |
289 | TEST_EQ(bl::translate(c.c_str(), original.c_str()).str(l), expected); |
290 | std::locale tmp_locale; |
291 | std::locale::global(loc: l); |
292 | string_type tmp = bl::translate(c, original); |
293 | TEST_EQ(tmp, expected); |
294 | tmp = bl::translate(c, original).str(); |
295 | TEST_EQ(tmp, expected); |
296 | std::locale::global(loc: tmp_locale); |
297 | |
298 | std::basic_ostringstream<Char> ss; |
299 | ss.imbue(l); |
300 | if(stream_translate(ss, c, original)) |
301 | TEST_EQ(ss.str(), expected); |
302 | } |
303 | TEST_EQ(bl::translate(c, original).str(l, domain), expected); |
304 | std::locale tmp_locale; |
305 | std::locale::global(loc: l); |
306 | TEST_EQ(bl::translate(c, original).str(domain), expected); |
307 | std::locale::global(loc: tmp_locale); |
308 | { |
309 | std::basic_ostringstream<Char> ss; |
310 | ss.imbue(l); |
311 | if(stream_translate(ss << bl::as::domain(id: domain), c, original)) |
312 | TEST_EQ(ss.str(), expected); |
313 | } |
314 | { |
315 | std::basic_ostringstream<Char> ss; |
316 | ss.imbue(l); |
317 | if(stream_translate(ss << bl::as::domain(id: domain), c.c_str(), original.c_str())) |
318 | TEST_EQ(ss.str(), expected); |
319 | } |
320 | } |
321 | |
322 | template<typename Char> |
323 | void test_translate(const std::string& sOriginal, |
324 | const std::string& sExpected, |
325 | const std::locale& l, |
326 | const std::string& domain) |
327 | { |
328 | typedef std::basic_string<Char> string_type; |
329 | const string_type expected = to_correct_string<Char>(sExpected, l); |
330 | const string_type original = to<Char>(sOriginal); |
331 | if(domain == "default" ) { |
332 | TEST_EQ(bl::translate(original).str(l), expected); |
333 | TEST_EQ(bl::translate(original.c_str()).str(l), expected); |
334 | std::locale tmp_locale; |
335 | std::locale::global(loc: l); |
336 | string_type tmp = bl::translate(original); |
337 | TEST_EQ(tmp, expected); |
338 | tmp = bl::translate(original).str(); |
339 | TEST_EQ(tmp, expected); |
340 | std::locale::global(loc: tmp_locale); |
341 | |
342 | std::basic_ostringstream<Char> ss; |
343 | ss.imbue(l); |
344 | if(stream_translate(ss, original)) |
345 | TEST_EQ(ss.str(), expected); |
346 | } |
347 | TEST_EQ(bl::translate(original).str(l, domain), expected); |
348 | std::locale tmp_locale; |
349 | std::locale::global(loc: l); |
350 | TEST_EQ(bl::translate(original).str(domain), expected); |
351 | std::locale::global(loc: tmp_locale); |
352 | { |
353 | std::basic_ostringstream<Char> ss; |
354 | ss.imbue(l); |
355 | if(stream_translate(ss << bl::as::domain(id: domain), original)) |
356 | TEST_EQ(ss.str(), expected); |
357 | } |
358 | { |
359 | std::basic_ostringstream<Char> ss; |
360 | ss.imbue(l); |
361 | if(stream_translate(ss << bl::as::domain(id: domain), original.c_str())) |
362 | TEST_EQ(ss.str(), expected); |
363 | } |
364 | } |
365 | } // namespace impl |
366 | |
367 | void test_cntranslate(const std::string& c, |
368 | const std::string& s, |
369 | const std::string& p, |
370 | long long n, |
371 | const std::string& expected, |
372 | const std::locale& l, |
373 | const std::string& domain) |
374 | { |
375 | std::cout << " char" << std::endl; |
376 | impl::test_cntranslate<char>(sContext: c, sSingular: s, sPlural: p, n, sExpected: expected, l, domain); |
377 | std::cout << " wchar_t" << std::endl; |
378 | impl::test_cntranslate<wchar_t>(sContext: c, sSingular: s, sPlural: p, n, sExpected: expected, l, domain); |
379 | #ifndef BOOST_LOCALE_NO_CXX20_STRING8 |
380 | std::cout << " char8_t" << std::endl; |
381 | impl::test_cntranslate<char8_t>(c, s, p, n, expected, l, domain); |
382 | #endif |
383 | #ifdef BOOST_LOCALE_ENABLE_CHAR16_T |
384 | std::cout << " char16_t" << std::endl; |
385 | impl::test_cntranslate<char16_t>(c, s, p, n, expected, l, domain); |
386 | #endif |
387 | #ifdef BOOST_LOCALE_ENABLE_CHAR32_T |
388 | std::cout << " char32_t" << std::endl; |
389 | impl::test_cntranslate<char32_t>(c, s, p, n, expected, l, domain); |
390 | #endif |
391 | } |
392 | |
393 | void test_ntranslate(const std::string& s, |
394 | const std::string& p, |
395 | long long n, |
396 | const std::string& expected, |
397 | const std::locale& l, |
398 | const std::string& domain) |
399 | { |
400 | std::cout << " char" << std::endl; |
401 | impl::test_ntranslate<char>(sSingular: s, sPlural: p, n, sExpected: expected, l, domain); |
402 | std::cout << " wchar_t" << std::endl; |
403 | impl::test_ntranslate<wchar_t>(sSingular: s, sPlural: p, n, sExpected: expected, l, domain); |
404 | #ifndef BOOST_LOCALE_NO_CXX20_STRING8 |
405 | std::cout << " char8_t" << std::endl; |
406 | impl::test_ntranslate<char8_t>(s, p, n, expected, l, domain); |
407 | #endif |
408 | #ifdef BOOST_LOCALE_ENABLE_CHAR16_T |
409 | std::cout << " char16_t" << std::endl; |
410 | impl::test_ntranslate<char16_t>(s, p, n, expected, l, domain); |
411 | #endif |
412 | #ifdef BOOST_LOCALE_ENABLE_CHAR32_T |
413 | std::cout << " char32_t" << std::endl; |
414 | impl::test_ntranslate<char32_t>(s, p, n, expected, l, domain); |
415 | #endif |
416 | } |
417 | |
418 | void test_ctranslate(const std::string& c, |
419 | const std::string& original, |
420 | const std::string& expected, |
421 | const std::locale& l, |
422 | const std::string& domain) |
423 | { |
424 | std::cout << " char" << std::endl; |
425 | impl::test_ctranslate<char>(sContext: c, sOriginal: original, sExpected: expected, l, domain); |
426 | std::cout << " wchar_t" << std::endl; |
427 | impl::test_ctranslate<wchar_t>(sContext: c, sOriginal: original, sExpected: expected, l, domain); |
428 | #ifndef BOOST_LOCALE_NO_CXX20_STRING8 |
429 | std::cout << " char8_t" << std::endl; |
430 | impl::test_ctranslate<char8_t>(c, original, expected, l, domain); |
431 | #endif |
432 | #ifdef BOOST_LOCALE_ENABLE_CHAR16_T |
433 | std::cout << " char16_t" << std::endl; |
434 | impl::test_ctranslate<char16_t>(c, original, expected, l, domain); |
435 | #endif |
436 | #ifdef BOOST_LOCALE_ENABLE_CHAR32_T |
437 | std::cout << " char32_t" << std::endl; |
438 | impl::test_ctranslate<char32_t>(c, original, expected, l, domain); |
439 | #endif |
440 | } |
441 | |
442 | void test_translate(const std::string& original, |
443 | const std::string& expected, |
444 | const std::locale& l, |
445 | const std::string& domain) |
446 | { |
447 | std::cout << " char" << std::endl; |
448 | impl::test_translate<char>(sOriginal: original, sExpected: expected, l, domain); |
449 | std::cout << " wchar_t" << std::endl; |
450 | impl::test_translate<wchar_t>(sOriginal: original, sExpected: expected, l, domain); |
451 | #ifndef BOOST_LOCALE_NO_CXX20_STRING8 |
452 | std::cout << " char8_t" << std::endl; |
453 | impl::test_translate<char8_t>(original, expected, l, domain); |
454 | #endif |
455 | #ifdef BOOST_LOCALE_ENABLE_CHAR16_T |
456 | std::cout << " char16_t" << std::endl; |
457 | impl::test_translate<char16_t>(original, expected, l, domain); |
458 | #endif |
459 | #ifdef BOOST_LOCALE_ENABLE_CHAR32_T |
460 | std::cout << " char32_t" << std::endl; |
461 | impl::test_translate<char32_t>(original, expected, l, domain); |
462 | #endif |
463 | } |
464 | |
465 | bool iso_8859_8_supported = true; |
466 | |
467 | void test_main(int argc, char** argv) |
468 | { |
469 | test_messages_info(); |
470 | |
471 | const std::string message_path = (argc == 2) ? argv[1] : "." ; |
472 | |
473 | for(const std::string& backend_name : boost::locale::localization_backend_manager::global().get_all_backends()) { |
474 | std::cout << "Testing for backend --------- " << backend_name << std::endl; |
475 | boost::locale::localization_backend_manager tmp_backend = boost::locale::localization_backend_manager::global(); |
476 | tmp_backend.select(backend_name); |
477 | boost::locale::localization_backend_manager::global(tmp_backend); |
478 | |
479 | backend = backend_name; |
480 | |
481 | boost::locale::generator g; |
482 | g.add_messages_domain(domain: "simple" ); |
483 | g.add_messages_domain(domain: "full" ); |
484 | // Fallback using only ASCII keys, choose a specific encoding != UTF-8, here: Latin1 |
485 | g.add_messages_domain(domain: "fall/ISO-8859-1" ); |
486 | g.add_messages_path(path: message_path); |
487 | g.set_default_messages_domain("default" ); |
488 | |
489 | for(const std::string locale_name : {"he_IL.UTF-8" , "he_IL.ISO8859-8" }) { |
490 | std::locale l; |
491 | |
492 | if(locale_name.find(s: ".ISO" ) != std::string::npos) { |
493 | try { |
494 | l = g(locale_name); |
495 | } catch(const boost::locale::conv::invalid_charset_error&) { // LCOV_EXCL_LINE |
496 | std::cout << "Looks like ISO-8859-8 is not supported! skipping" << std::endl; // LCOV_EXCL_LINE |
497 | iso_8859_8_supported = false; // LCOV_EXCL_LINE |
498 | continue; // LCOV_EXCL_LINE |
499 | } |
500 | } else |
501 | l = g(locale_name); |
502 | |
503 | std::cout << " Testing " << locale_name << std::endl; |
504 | std::cout << " single forms" << std::endl; |
505 | |
506 | test_translate(original: "hello" , expected: "שלום" , l, domain: "default" ); |
507 | test_translate(original: "hello" , expected: "היי" , l, domain: "simple" ); |
508 | test_translate(original: "hello" , expected: "hello" , l, domain: "undefined" ); |
509 | test_translate(original: "untranslated" , expected: "untranslated" , l, domain: "default" ); |
510 | // Check removal of old "context" information |
511 | test_translate(original: "#untranslated" , expected: "#untranslated" , l, domain: "default" ); |
512 | test_translate(original: "##untranslated" , expected: "##untranslated" , l, domain: "default" ); |
513 | test_ctranslate(c: "context" , original: "hello" , expected: "שלום בהקשר אחר" , l, domain: "default" ); |
514 | test_translate(original: "#hello" , expected: "#שלום" , l, domain: "default" ); |
515 | |
516 | std::cout << " plural forms" << std::endl; |
517 | |
518 | { |
519 | test_ntranslate(s: "x day" , p: "x days" , n: 0, expected: "x ימים" , l, domain: "default" ); |
520 | test_ntranslate(s: "x day" , p: "x days" , n: 1, expected: "יום x" , l, domain: "default" ); |
521 | test_ntranslate(s: "x day" , p: "x days" , n: 2, expected: "יומיים" , l, domain: "default" ); |
522 | test_ntranslate(s: "x day" , p: "x days" , n: 3, expected: "x ימים" , l, domain: "default" ); |
523 | test_ntranslate(s: "x day" , p: "x days" , n: 20, expected: "x יום" , l, domain: "default" ); |
524 | test_ntranslate(s: "x day" , p: "x days" , n: 0, expected: "x days" , l, domain: "undefined" ); |
525 | test_ntranslate(s: "x day" , p: "x days" , n: 1, expected: "x day" , l, domain: "undefined" ); |
526 | test_ntranslate(s: "x day" , p: "x days" , n: 2, expected: "x days" , l, domain: "undefined" ); |
527 | test_ntranslate(s: "x day" , p: "x days" , n: 20, expected: "x days" , l, domain: "undefined" ); |
528 | // Ensure no truncation occurs |
529 | test_ntranslate(s: "x day" , p: "x days" , n: std::numeric_limits<long long>::min(), expected: "x days" , l, domain: "undefined" ); |
530 | test_ntranslate(s: "x day" , p: "x days" , n: std::numeric_limits<long long>::max(), expected: "x days" , l, domain: "undefined" ); |
531 | for(unsigned bit = 1; bit < std::numeric_limits<long long>::digits; ++bit) { |
532 | // Set each individual bit possible and add 1. |
533 | // If the value is truncated the 1 will remain leading to singular form |
534 | const auto num = static_cast<long long>(static_cast<unsigned long long>(1) << bit); |
535 | test_ntranslate(s: "x day" , p: "x days" , n: num + 1, expected: "x days" , l, domain: "undefined" ); |
536 | } |
537 | } |
538 | std::cout << " plural forms with context" << std::endl; |
539 | { |
540 | std::string inp = "context" ; |
541 | std::string out = "בהקשר " ; |
542 | |
543 | test_cntranslate(c: inp, s: "x day" , p: "x days" , n: 0, expected: out + "x ימים" , l, domain: "default" ); |
544 | test_cntranslate(c: inp, s: "x day" , p: "x days" , n: 1, expected: out + "יום x" , l, domain: "default" ); |
545 | test_cntranslate(c: inp, s: "x day" , p: "x days" , n: 2, expected: out + "יומיים" , l, domain: "default" ); |
546 | test_cntranslate(c: inp, s: "x day" , p: "x days" , n: 3, expected: out + "x ימים" , l, domain: "default" ); |
547 | test_cntranslate(c: inp, s: "x day" , p: "x days" , n: 20, expected: out + "x יום" , l, domain: "default" ); |
548 | |
549 | test_cntranslate(c: inp, s: "x day" , p: "x days" , n: 0, expected: "x days" , l, domain: "undefined" ); |
550 | test_cntranslate(c: inp, s: "x day" , p: "x days" , n: 1, expected: "x day" , l, domain: "undefined" ); |
551 | test_cntranslate(c: inp, s: "x day" , p: "x days" , n: 2, expected: "x days" , l, domain: "undefined" ); |
552 | test_cntranslate(c: inp, s: "x day" , p: "x days" , n: 20, expected: "x days" , l, domain: "undefined" ); |
553 | } |
554 | } |
555 | std::cout << " Testing fallbacks" << std::endl; |
556 | { |
557 | const std::locale l = g("he_IL.UTF-8" ); |
558 | test_translate(original: "test" , expected: "he_IL" , l, domain: "full" ); |
559 | test_translate(original: "test" , expected: "he" , l, domain: "fall" ); |
560 | for(int n = -1; n < 5; ++n) { |
561 | // No plural forms -> Use english logic |
562 | // Singular is translated, plural is not |
563 | test_ntranslate(s: "test" , p: "tests" , n, expected: (n == 1) ? "he" : "tests" , l, domain: "fall" ); |
564 | } |
565 | } |
566 | |
567 | std::cout << " Testing automatic conversions " << std::endl; |
568 | std::locale::global(loc: g("he_IL.UTF-8" )); |
569 | |
570 | TEST_EQ(same_s(bl::translate("hello" )), "שלום" ); |
571 | TEST_EQ(same_w(bl::translate(to<wchar_t>("hello" ))), to<wchar_t>("שלום" )); |
572 | |
573 | #ifndef BOOST_LOCALE_NO_CXX20_STRING8 |
574 | TEST_EQ(same_u8(bl::translate(to<char8_t>("hello" ))), to<char8_t>("שלום" )); |
575 | #endif |
576 | #ifdef BOOST_LOCALE_ENABLE_CHAR16_T |
577 | if(backend == "icu" || backend == "std" ) |
578 | TEST_EQ(same_u16(bl::translate(to<char16_t>("hello" ))), to<char16_t>("שלום" )); |
579 | #endif |
580 | #ifdef BOOST_LOCALE_ENABLE_CHAR32_T |
581 | if(backend == "icu" || backend == "std" ) |
582 | TEST_EQ(same_u32(bl::translate(to<char32_t>("hello" ))), to<char32_t>("שלום" )); |
583 | #endif |
584 | } |
585 | |
586 | std::cout << "Testing custom file system support" << std::endl; |
587 | { |
588 | boost::locale::gnu_gettext::messages_info info; |
589 | info.language = "he" ; |
590 | info.country = "IL" ; |
591 | info.encoding = "UTF-8" ; |
592 | info.paths.push_back(x: message_path); |
593 | |
594 | info.domains.push_back(x: bl::gnu_gettext::messages_info::domain("default" )); |
595 | info.callback = file_loader(); |
596 | |
597 | file_loader_is_actually_called = false; |
598 | std::locale l(std::locale::classic(), boost::locale::gnu_gettext::create_messages_facet<char>(info)); |
599 | TEST(file_loader_is_actually_called); |
600 | TEST_EQ(bl::translate("hello" ).str(l), "שלום" ); |
601 | } |
602 | if(iso_8859_8_supported) { |
603 | std::cout << "Testing non-US-ASCII keys" << std::endl; |
604 | std::cout << " UTF-8 keys" << std::endl; |
605 | { |
606 | boost::locale::generator g; |
607 | g.add_messages_domain(domain: "default" ); |
608 | g.add_messages_path(path: message_path); |
609 | |
610 | std::locale l = g("he_IL.UTF-8" ); |
611 | |
612 | // narrow |
613 | TEST_EQ(bl::gettext("בדיקה" , l), "test" ); |
614 | TEST_EQ(bl::gettext("לא קיים" , l), "לא קיים" ); |
615 | |
616 | // wide |
617 | std::wstring wtest = bl::conv::utf_to_utf<wchar_t>(str: "בדיקה" ); |
618 | std::wstring wmiss = bl::conv::utf_to_utf<wchar_t>(str: "לא קיים" ); |
619 | TEST_EQ(bl::gettext(wtest.c_str(), l), L"test" ); |
620 | TEST_EQ(bl::gettext(wmiss.c_str(), l), wmiss); |
621 | |
622 | l = g("he_IL.ISO-8859-8" ); |
623 | |
624 | // conversion with substitution |
625 | TEST_EQ(bl::gettext("test-あにま-בדיקה" , l), bl::conv::from_utf("test--בדיקה" , "ISO-8859-8" )); |
626 | } |
627 | |
628 | std::cout << " ANSI keys" << std::endl; |
629 | |
630 | { |
631 | boost::locale::generator g; |
632 | g.add_messages_domain(domain: "default/ISO-8859-8" ); |
633 | g.add_messages_path(path: message_path); |
634 | |
635 | std::locale l = g("he_IL.UTF-8" ); |
636 | |
637 | // narrow non-UTF-8 keys |
638 | // match |
639 | TEST_EQ(bl::gettext(bl::conv::from_utf("בדיקה" , "ISO-8859-8" ).c_str(), l), "test" ); |
640 | // conversion |
641 | TEST_EQ(bl::gettext(bl::conv::from_utf("לא קיים" , "ISO-8859-8" ).c_str(), l), "לא קיים" ); |
642 | } |
643 | } |
644 | // Test compiles |
645 | { |
646 | bl::gettext(id: "" ); |
647 | bl::gettext(id: L"" ); |
648 | bl::dgettext(domain: "" , id: "" ); |
649 | bl::dgettext(domain: "" , id: L"" ); |
650 | |
651 | bl::pgettext(context: "" , id: "" ); |
652 | bl::pgettext(context: L"" , id: L"" ); |
653 | bl::dpgettext(domain: "" , context: "" , id: "" ); |
654 | bl::dpgettext(domain: "" , context: L"" , id: L"" ); |
655 | |
656 | bl::ngettext(s: "" , p: "" , n: 1); |
657 | bl::ngettext(s: L"" , p: L"" , n: 1); |
658 | bl::dngettext(domain: "" , s: "" , p: "" , n: 1); |
659 | bl::dngettext(domain: "" , s: L"" , p: L"" , n: 1); |
660 | |
661 | bl::npgettext(context: "" , s: "" , p: "" , n: 1); |
662 | bl::npgettext(context: L"" , s: L"" , p: L"" , n: 1); |
663 | bl::dnpgettext(domain: "" , context: "" , s: "" , p: "" , n: 1); |
664 | bl::dnpgettext(domain: "" , context: L"" , s: L"" , p: L"" , n: 1); |
665 | } |
666 | } |
667 | |
668 | // boostinspect:noascii |
669 | |