From fadc6b9bac82e99a422d72935a74f3a5444274ed Mon Sep 17 00:00:00 2001 From: MarcoFalke <*~=`'#}+{/-|&$^_@721217.xyz> Date: Wed, 9 Oct 2024 13:24:51 +0200 Subject: [PATCH] refactor: Check translatable format strings at compile-time --- src/test/fuzz/strprintf.cpp | 15 --------------- src/test/translation_tests.cpp | 19 ++++++++++++++++--- src/util/translation.h | 28 ++++++++++++++++++++++------ 3 files changed, 38 insertions(+), 24 deletions(-) diff --git a/src/test/fuzz/strprintf.cpp b/src/test/fuzz/strprintf.cpp index 18de0e1960d..be40e88cdd4 100644 --- a/src/test/fuzz/strprintf.cpp +++ b/src/test/fuzz/strprintf.cpp @@ -18,7 +18,6 @@ FUZZ_TARGET(str_printf) { FuzzedDataProvider fuzzed_data_provider(buffer.data(), buffer.size()); const std::string format_string = fuzzed_data_provider.ConsumeRandomLengthString(64); - const bilingual_str bilingual_string{format_string, format_string}; const int digits_in_format_specifier = std::count_if(format_string.begin(), format_string.end(), IsDigit); @@ -53,27 +52,21 @@ FUZZ_TARGET(str_printf) fuzzed_data_provider, [&] { (void)strprintf(format_string, fuzzed_data_provider.ConsumeRandomLengthString(32)); - (void)tinyformat::format(bilingual_string, fuzzed_data_provider.ConsumeRandomLengthString(32)); }, [&] { (void)strprintf(format_string, fuzzed_data_provider.ConsumeRandomLengthString(32).c_str()); - (void)tinyformat::format(bilingual_string, fuzzed_data_provider.ConsumeRandomLengthString(32).c_str()); }, [&] { (void)strprintf(format_string, fuzzed_data_provider.ConsumeIntegral()); - (void)tinyformat::format(bilingual_string, fuzzed_data_provider.ConsumeIntegral()); }, [&] { (void)strprintf(format_string, fuzzed_data_provider.ConsumeIntegral()); - (void)tinyformat::format(bilingual_string, fuzzed_data_provider.ConsumeIntegral()); }, [&] { (void)strprintf(format_string, fuzzed_data_provider.ConsumeIntegral()); - (void)tinyformat::format(bilingual_string, fuzzed_data_provider.ConsumeIntegral()); }, [&] { (void)strprintf(format_string, fuzzed_data_provider.ConsumeBool()); - (void)tinyformat::format(bilingual_string, fuzzed_data_provider.ConsumeBool()); }); } catch (const tinyformat::format_error&) { } @@ -99,35 +92,27 @@ FUZZ_TARGET(str_printf) fuzzed_data_provider, [&] { (void)strprintf(format_string, fuzzed_data_provider.ConsumeFloatingPoint()); - (void)tinyformat::format(bilingual_string, fuzzed_data_provider.ConsumeFloatingPoint()); }, [&] { (void)strprintf(format_string, fuzzed_data_provider.ConsumeFloatingPoint()); - (void)tinyformat::format(bilingual_string, fuzzed_data_provider.ConsumeFloatingPoint()); }, [&] { (void)strprintf(format_string, fuzzed_data_provider.ConsumeIntegral()); - (void)tinyformat::format(bilingual_string, fuzzed_data_provider.ConsumeIntegral()); }, [&] { (void)strprintf(format_string, fuzzed_data_provider.ConsumeIntegral()); - (void)tinyformat::format(bilingual_string, fuzzed_data_provider.ConsumeIntegral()); }, [&] { (void)strprintf(format_string, fuzzed_data_provider.ConsumeIntegral()); - (void)tinyformat::format(bilingual_string, fuzzed_data_provider.ConsumeIntegral()); }, [&] { (void)strprintf(format_string, fuzzed_data_provider.ConsumeIntegral()); - (void)tinyformat::format(bilingual_string, fuzzed_data_provider.ConsumeIntegral()); }, [&] { (void)strprintf(format_string, fuzzed_data_provider.ConsumeIntegral()); - (void)tinyformat::format(bilingual_string, fuzzed_data_provider.ConsumeIntegral()); }, [&] { (void)strprintf(format_string, fuzzed_data_provider.ConsumeIntegral()); - (void)tinyformat::format(bilingual_string, fuzzed_data_provider.ConsumeIntegral()); }); } catch (const tinyformat::format_error&) { } diff --git a/src/test/translation_tests.cpp b/src/test/translation_tests.cpp index bda5dfd0994..4935830cdc2 100644 --- a/src/test/translation_tests.cpp +++ b/src/test/translation_tests.cpp @@ -9,13 +9,26 @@ BOOST_AUTO_TEST_SUITE(translation_tests) +static TranslateFn translate{[](const char * str) { return strprintf("t(%s)", str); }}; + +// Custom translation function _t(), similar to _() but internal to this test. +consteval auto _t(util::TranslatedLiteral str) +{ + str.translate_fn = &translate; + return str; +} + BOOST_AUTO_TEST_CASE(translation_namedparams) { bilingual_str arg{"original", "translated"}; - bilingual_str format{"original [%s]", "translated [%s]"}; - bilingual_str result{strprintf(format, arg)}; + bilingual_str result{strprintf(_t("original [%s]"), arg)}; BOOST_CHECK_EQUAL(result.original, "original [original]"); - BOOST_CHECK_EQUAL(result.translated, "translated [translated]"); + BOOST_CHECK_EQUAL(result.translated, "t(original [translated])"); + + util::TranslatedLiteral arg2{"original", &translate}; + bilingual_str result2{strprintf(_t("original [%s]"), arg2)}; + BOOST_CHECK_EQUAL(result2.original, "original [original]"); + BOOST_CHECK_EQUAL(result2.translated, "t(original [t(original)])"); } BOOST_AUTO_TEST_SUITE_END() diff --git a/src/util/translation.h b/src/util/translation.h index c58f7c26619..27747a16f2c 100644 --- a/src/util/translation.h +++ b/src/util/translation.h @@ -67,6 +67,13 @@ template T operator+(const T& lhs, const TranslatedLiteral& rhs) { return lhs + static_cast(rhs); } template T operator+(const TranslatedLiteral& lhs, const T& rhs) { return static_cast(lhs) + rhs; } + +template +struct BilingualFmt { + const ConstevalFormatString original; + TranslatedLiteral lit; + consteval BilingualFmt(TranslatedLiteral l) : original{l.original}, lit{l} {} +}; } // namespace util consteval auto _(util::TranslatedLiteral str) { return str; } @@ -74,20 +81,29 @@ consteval auto _(util::TranslatedLiteral str) { return str; } /** Mark a bilingual_str as untranslated */ inline bilingual_str Untranslated(std::string original) { return {original, original}; } -// Provide an overload of tinyformat::format which can take bilingual_str arguments. +// Provide an overload of tinyformat::format for BilingualFmt format strings and bilingual_str or TranslatedLiteral args. namespace tinyformat { template -bilingual_str format(const bilingual_str& fmt, const Args&... args) +bilingual_str format(util::BilingualFmt fmt, const Args&... args) { - const auto translate_arg{[](const auto& arg, bool translated) -> const auto& { + const auto original_arg{[](const auto& arg) -> const auto& { if constexpr (std::is_same_v) { - return translated ? arg.translated : arg.original; + return arg.original; + } else if constexpr (std::is_same_v) { + return arg.original; } else { return arg; } }}; - return bilingual_str{tfm::format(fmt.original, translate_arg(args, false)...), - tfm::format(fmt.translated, translate_arg(args, true)...)}; + const auto translated_arg{[](const auto& arg) -> const auto& { + if constexpr (std::is_same_v) { + return arg.translated; + } else { + return arg; + } + }}; + return bilingual_str{tfm::format(fmt.original, original_arg(args)...), + tfm::format(std::string{fmt.lit}, translated_arg(args)...)}; } } // namespace tinyformat