From bf693e2609941b6ae18d6c619b2aa333ff33980f Mon Sep 17 00:00:00 2001 From: Andras Lasso Date: Thu, 21 Mar 2024 21:40:34 -0400 Subject: [PATCH] ENH: Add helper function for MRML translation with placeholders --- Libs/MRML/Core/Testing/vtkMRMLI18NTest1.cxx | 7 ++++ Libs/MRML/Core/vtkMRMLI18N.cxx | 42 +++++++++++++++++++++ Libs/MRML/Core/vtkMRMLI18N.h | 18 +++++++++ 3 files changed, 67 insertions(+) diff --git a/Libs/MRML/Core/Testing/vtkMRMLI18NTest1.cxx b/Libs/MRML/Core/Testing/vtkMRMLI18NTest1.cxx index a525c4c8ce9..d0140da2de7 100644 --- a/Libs/MRML/Core/Testing/vtkMRMLI18NTest1.cxx +++ b/Libs/MRML/Core/Testing/vtkMRMLI18NTest1.cxx @@ -58,5 +58,12 @@ int vtkMRMLI18NTest1(int, char*[]) // Use translation convenience function CHECK_STD_STRING(vtkMRMLTr("SomeContext", "SomeMessage"), "translated-SomeContextSomeMessage"); + CHECK_STD_STRING(vtkMRMLI18N::Format("Some text without replacement", "aaa"), "Some text without replacement"); + CHECK_STD_STRING(vtkMRMLI18N::Format("Some text with %1 replacement", "aaa"), "Some text with aaa replacement"); + CHECK_STD_STRING(vtkMRMLI18N::Format("Some text %2 with %1 replacement", "aaa", "qwerty"), "Some text qwerty with aaa replacement"); + CHECK_STD_STRING(vtkMRMLI18N::Format("Some text with missing %1 replacement %2 end", "aaa"), "Some text with missing aaa replacement end"); + CHECK_STD_STRING(vtkMRMLI18N::Format("Some %2 with %1 escaping %%2 and %% end", "aaa", "qwerty"), "Some qwerty with aaa escaping %2 and % end"); + CHECK_STD_STRING(vtkMRMLI18N::Format("Some text edge case %", "aaa"), "Some text edge case %"); + return EXIT_SUCCESS; } diff --git a/Libs/MRML/Core/vtkMRMLI18N.cxx b/Libs/MRML/Core/vtkMRMLI18N.cxx index cffd4f0d43b..cab2f1f61b6 100644 --- a/Libs/MRML/Core/vtkMRMLI18N.cxx +++ b/Libs/MRML/Core/vtkMRMLI18N.cxx @@ -147,3 +147,45 @@ std::string vtkMRMLI18N::Translate(const char *context, const char *sourceText, return sourceText ? sourceText : ""; } } + +//---------------------------------------------------------------------------- +std::string vtkMRMLI18N::Format(const std::string& input, + const char* arg1/*=nullptr*/, const char* arg2/*=nullptr*/, const char* arg3/*=nullptr*/, + const char* arg4/*=nullptr*/, const char* arg5/*=nullptr*/, const char* arg6/*=nullptr*/, + const char* arg7/*=nullptr*/, const char* arg8/*=nullptr*/, const char* arg9/*=nullptr*/) +{ + std::string output; + + for (std::string::const_iterator it = input.cbegin(); it != input.cend(); ++it) + { + if ((*it == '%') && (it + 1 != input.end())) + { + const char nextChar = *(it + 1); + if (nextChar == '%') + { + // Escaped '%' + output += "%"; + ++it; + continue; + } + else if (std::isdigit(nextChar)) + { + // Found a valid placeholder (e.g., %1, %2, %3, ..., %9) + if (nextChar == '1' && arg1) { output += arg1; } + else if (nextChar == '2' && arg2) { output += arg2; } + else if (nextChar == '3' && arg3) { output += arg3; } + else if (nextChar == '4' && arg4) { output += arg4; } + else if (nextChar == '5' && arg5) { output += arg5; } + else if (nextChar == '6' && arg6) { output += arg6; } + else if (nextChar == '7' && arg7) { output += arg7; } + else if (nextChar == '8' && arg8) { output += arg8; } + else if (nextChar == '9' && arg9) { output += arg9; } + ++it; + continue; + } + } + output += (*it); + } + + return output; +} diff --git a/Libs/MRML/Core/vtkMRMLI18N.h b/Libs/MRML/Core/vtkMRMLI18N.h index ae487195cda..6944b184883 100644 --- a/Libs/MRML/Core/vtkMRMLI18N.h +++ b/Libs/MRML/Core/vtkMRMLI18N.h @@ -50,6 +50,24 @@ class VTK_MRML_EXPORT vtkMRMLI18N : public vtkObject /// Translate message with the current translator static std::string Translate(const char *context, const char *sourceText, const char *disambiguation = nullptr, int n = -1); + /// Replace placeholders in strings, following Qt internationalization conventions. + /// + /// Accepted placeholders in the input string: %1, %2, %3, ..., %9. + /// Use %% instead of a single % to prevent replacement. For example "some %%3 thing" will result in "some %3 thing" + /// (and will not be replaced by the third replacement string argument). + /// + /// Example usage: + /// @code + /// std::string displayableText = vtkMRMLI18N::Format( + /// vtkMRMLTr("vtkMRMLVolumeArchetypeStorageNode", "Cannot read '%1' file as a volume of type '%2'."), + /// filename.c_str(), + /// volumeType.c_str()); + /// @endcode + static std::string Format(const std::string& text, + const char* arg1 = nullptr, const char* arg2 = nullptr, const char* arg3 = nullptr, + const char* arg4 = nullptr, const char* arg5 = nullptr, const char* arg6 = nullptr, + const char* arg7 = nullptr, const char* arg8 = nullptr, const char* arg9 = nullptr); + /// Set translator object. This class takes ownership of the translator /// and it releases it when the process quits. void SetTranslator(vtkMRMLTranslator* translator);