Common: Assert that translatable strings use positional arguments
Like PR 9260, but for a different requirement (see PR 9253).
This commit is contained in:
parent
e63b00e562
commit
e4ceed2bed
|
@ -38,4 +38,39 @@ constexpr std::size_t CountFmtReplacementFields(std::string_view s)
|
||||||
static_assert(CountFmtReplacementFields("") == 0);
|
static_assert(CountFmtReplacementFields("") == 0);
|
||||||
static_assert(CountFmtReplacementFields("{} test {:x}") == 2);
|
static_assert(CountFmtReplacementFields("{} test {:x}") == 2);
|
||||||
static_assert(CountFmtReplacementFields("{} {{}} test {{{}}}") == 2);
|
static_assert(CountFmtReplacementFields("{} {{}} test {{{}}}") == 2);
|
||||||
|
|
||||||
|
constexpr bool ContainsNonPositionalArguments(std::string_view s)
|
||||||
|
{
|
||||||
|
for (std::size_t i = 0; i < s.size(); ++i)
|
||||||
|
{
|
||||||
|
if (s[i] != '{' || i + 1 == s.size())
|
||||||
|
continue;
|
||||||
|
|
||||||
|
const char next = s[i + 1];
|
||||||
|
|
||||||
|
// If the opening brace is followed by another brace, what we have is
|
||||||
|
// an escaped brace, not a replacement field.
|
||||||
|
if (next == '{')
|
||||||
|
{
|
||||||
|
// Skip the second brace.
|
||||||
|
// This ensures that e.g. {{{}}} is counted correctly: when the first brace character
|
||||||
|
// is read and detected as being part of an '{{' escape sequence, the second character
|
||||||
|
// is skipped so the most inner brace (the third character) is not detected
|
||||||
|
// as the end of an '{{' pair.
|
||||||
|
++i;
|
||||||
|
}
|
||||||
|
else if (next == '}' || next == ':')
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static_assert(!ContainsNonPositionalArguments(""));
|
||||||
|
static_assert(ContainsNonPositionalArguments("{}"));
|
||||||
|
static_assert(!ContainsNonPositionalArguments("{0}"));
|
||||||
|
static_assert(ContainsNonPositionalArguments("{:x}"));
|
||||||
|
static_assert(!ContainsNonPositionalArguments("{0:x}"));
|
||||||
|
static_assert(!ContainsNonPositionalArguments("{0} {{}} test {{{1}}}"));
|
||||||
} // namespace Common
|
} // namespace Common
|
||||||
|
|
|
@ -102,6 +102,16 @@ std::string FmtFormatT(const char* string, Args&&... args)
|
||||||
##__VA_ARGS__); \
|
##__VA_ARGS__); \
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
#define GenericAlertFmtT(yes_no, style, format, ...) \
|
||||||
|
[&] { \
|
||||||
|
static_assert(!Common::ContainsNonPositionalArguments(format), \
|
||||||
|
"Translatable strings must use positional arguments (e.g. {0} instead of {})"); \
|
||||||
|
/* Use a macro-like name to avoid shadowing warnings */ \
|
||||||
|
constexpr auto GENERIC_ALERT_FMT_N = Common::CountFmtReplacementFields(format); \
|
||||||
|
return Common::MsgAlertFmt<GENERIC_ALERT_FMT_N>(yes_no, style, FMT_STRING(format), \
|
||||||
|
##__VA_ARGS__); \
|
||||||
|
}()
|
||||||
|
|
||||||
#define SuccessAlertFmt(format, ...) \
|
#define SuccessAlertFmt(format, ...) \
|
||||||
GenericAlertFmt(false, Common::MsgType::Information, format, ##__VA_ARGS__)
|
GenericAlertFmt(false, Common::MsgType::Information, format, ##__VA_ARGS__)
|
||||||
|
|
||||||
|
@ -119,16 +129,16 @@ std::string FmtFormatT(const char* string, Args&&... args)
|
||||||
|
|
||||||
// Use these macros (that do the same thing) if the message should be translated.
|
// Use these macros (that do the same thing) if the message should be translated.
|
||||||
#define SuccessAlertFmtT(format, ...) \
|
#define SuccessAlertFmtT(format, ...) \
|
||||||
GenericAlertFmt(false, Common::MsgType::Information, format, ##__VA_ARGS__)
|
GenericAlertFmtT(false, Common::MsgType::Information, format, ##__VA_ARGS__)
|
||||||
|
|
||||||
#define PanicAlertFmtT(format, ...) \
|
#define PanicAlertFmtT(format, ...) \
|
||||||
GenericAlertFmt(false, Common::MsgType::Warning, format, ##__VA_ARGS__)
|
GenericAlertFmtT(false, Common::MsgType::Warning, format, ##__VA_ARGS__)
|
||||||
|
|
||||||
#define PanicYesNoFmtT(format, ...) \
|
#define PanicYesNoFmtT(format, ...) \
|
||||||
GenericAlertFmt(true, Common::MsgType::Warning, format, ##__VA_ARGS__)
|
GenericAlertFmtT(true, Common::MsgType::Warning, format, ##__VA_ARGS__)
|
||||||
|
|
||||||
#define AskYesNoFmtT(format, ...) \
|
#define AskYesNoFmtT(format, ...) \
|
||||||
GenericAlertFmt(true, Common::MsgType::Question, format, ##__VA_ARGS__)
|
GenericAlertFmtT(true, Common::MsgType::Question, format, ##__VA_ARGS__)
|
||||||
|
|
||||||
#define CriticalAlertFmtT(format, ...) \
|
#define CriticalAlertFmtT(format, ...) \
|
||||||
GenericAlertFmt(false, Common::MsgType::Critical, format, ##__VA_ARGS__)
|
GenericAlertFmtT(false, Common::MsgType::Critical, format, ##__VA_ARGS__)
|
||||||
|
|
Loading…
Reference in New Issue