From 477ba0e7f16cbdc76730bce9eb537dff1705a54f Mon Sep 17 00:00:00 2001 From: Mouhamed DIOP Date: Wed, 23 Aug 2023 07:11:08 -0400 Subject: [PATCH] BUG: Enable language translation in MRML classes vtkMRMLI18N singleton class is responsible for internationalization related features in MRML. It currently only stores a language translator. A translator that uses Qt for translation is specified in qSlicerCoreApplication. Enables fixing of these issues: Slicer/SlicerLanguagePacks#12 Slicer#6647 Slicer#6177 Co-authored-by: Mouhamed DIOP --- Base/QTCore/qSlicerCoreApplication.cxx | 30 ++++ Libs/MRML/Core/CMakeLists.txt | 3 + Libs/MRML/Core/Testing/CMakeLists.txt | 1 + Libs/MRML/Core/Testing/vtkMRMLI18NTest1.cxx | 61 ++++++++ Libs/MRML/Core/vtkMRMLI18N.cxx | 149 ++++++++++++++++++++ Libs/MRML/Core/vtkMRMLI18N.h | 98 +++++++++++++ Libs/MRML/Core/vtkMRMLTranslator.h | 49 +++++++ 7 files changed, 391 insertions(+) create mode 100644 Libs/MRML/Core/Testing/vtkMRMLI18NTest1.cxx create mode 100644 Libs/MRML/Core/vtkMRMLI18N.cxx create mode 100644 Libs/MRML/Core/vtkMRMLI18N.h create mode 100644 Libs/MRML/Core/vtkMRMLTranslator.h diff --git a/Base/QTCore/qSlicerCoreApplication.cxx b/Base/QTCore/qSlicerCoreApplication.cxx index 22df082e842..261a62a2161 100644 --- a/Base/QTCore/qSlicerCoreApplication.cxx +++ b/Base/QTCore/qSlicerCoreApplication.cxx @@ -103,7 +103,9 @@ #ifdef Slicer_BUILD_CLI_SUPPORT # include #endif +#include #include +#include // CTK includes #include @@ -153,6 +155,30 @@ #include #endif +//----------------------------------------------------------------------------- +// Adapter class for translation in MRML classes using Qt translation infrastructure + +class vtkQtTranslator: public vtkMRMLTranslator +{ +public: + static vtkQtTranslator* New(); + vtkTypeMacro(vtkQtTranslator, vtkMRMLTranslator); + + /// Translation function for logic classes + std::string Translate(const char *context, const char *sourceText, const char *disambiguation = nullptr, int n = -1) override + { + return QCoreApplication::translate(context, sourceText, disambiguation, n).toStdString(); + } + +protected: + vtkQtTranslator() = default; + ~vtkQtTranslator() override = default; + vtkQtTranslator(const vtkQtTranslator&) = delete; + void operator=(const vtkQtTranslator&) = delete; +}; + +vtkStandardNewMacro(vtkQtTranslator); + //----------------------------------------------------------------------------- // Helper function @@ -353,6 +379,10 @@ void qSlicerCoreApplicationPrivate::init() vtkEventBroker::GetInstance()->SetRequestModifiedCallback(modifiedRequestCallback); } + // Set up translation in MRML classes using Qt translator + vtkNew mrmlTranslator; + vtkMRMLI18N::GetInstance()->SetTranslator(mrmlTranslator); + // Ensure that temporary folder is writable { // QTemporaryFile is deleted automatically when leaving this scope diff --git a/Libs/MRML/Core/CMakeLists.txt b/Libs/MRML/Core/CMakeLists.txt index 540721fe4f9..9e3d059bf9d 100644 --- a/Libs/MRML/Core/CMakeLists.txt +++ b/Libs/MRML/Core/CMakeLists.txt @@ -139,6 +139,8 @@ set(MRMLCore_SRCS vtkCodedEntry.cxx vtkEventBroker.cxx vtkDataFileFormatHelper.cxx + vtkMRMLI18N.cxx + vtkMRMLI18N.h vtkMRMLMeasurement.cxx vtkMRMLStaticMeasurement.cxx vtkMRMLLogic.cxx @@ -221,6 +223,7 @@ set(MRMLCore_SRCS vtkMRMLTransformStorageNode.cxx vtkMRMLTransformDisplayNode.cxx vtkMRMLTransformableNode.cxx + vtkMRMLTranslator.h vtkMRMLUnitNode.cxx vtkMRMLVectorVolumeDisplayNode.cxx vtkMRMLViewNode.cxx diff --git a/Libs/MRML/Core/Testing/CMakeLists.txt b/Libs/MRML/Core/Testing/CMakeLists.txt index a366e3f971c..88f2d983e62 100644 --- a/Libs/MRML/Core/Testing/CMakeLists.txt +++ b/Libs/MRML/Core/Testing/CMakeLists.txt @@ -31,6 +31,7 @@ create_test_sourcelist(Tests ${KIT}CxxTests.cxx vtkMRMLGridTransformNodeTest1.cxx vtkMRMLHierarchyNodeTest1.cxx vtkMRMLHierarchyNodeTest3.cxx + vtkMRMLI18NTest1.cxx vtkMRMLInteractionNodeTest1.cxx vtkMRMLLabelMapVolumeDisplayNodeTest1.cxx vtkMRMLLayoutNodeTest1.cxx diff --git a/Libs/MRML/Core/Testing/vtkMRMLI18NTest1.cxx b/Libs/MRML/Core/Testing/vtkMRMLI18NTest1.cxx new file mode 100644 index 00000000000..09309299443 --- /dev/null +++ b/Libs/MRML/Core/Testing/vtkMRMLI18NTest1.cxx @@ -0,0 +1,61 @@ +/*=auto========================================================================= + + Portions (c) Copyright 2005 Brigham and Women's Hospital (BWH) + All Rights Reserved. + + See COPYRIGHT.txt + or http://www.slicer.org/copyright/copyright.txt for details. + + Program: 3D Slicer + +=========================================================================auto=*/ + +// MRML includes +#include "vtkMRMLCoreTestingMacros.h" +#include "vtkMRMLScene.h" + +// VTK includes +#include +#include + +namespace +{ + + class vtkTestTranslator : public vtkMRMLTranslator + { + public: + static vtkTestTranslator * New(); + vtkTypeMacro(vtkTestTranslator, vtkMRMLTranslator); + + /// Translation method for testing that returns "translated-(context)(sourceText)" as translation + std::string Translate(const char* context, const char* sourceText, const char* disambiguation = nullptr, int n = -1) override + { + return std::string("translated-") + context + sourceText; + } + + protected: + vtkTestTranslator () = default; + ~vtkTestTranslator () override = default; + vtkTestTranslator (const vtkTestTranslator &) = delete; + void operator=(const vtkTestTranslator &) = delete; + }; + + vtkStandardNewMacro(vtkTestTranslator ); +} + +int vtkMRMLI18NTest1(int, char*[]) +{ + // Check default translation (simply sourcetext is returned) + CHECK_STD_STRING(vtkMRMLI18N::Translate("SomeContext", "SomeMessage"), "SomeMessage"); + + // Set custom translator + vtkNew translator; + vtkMRMLI18N::GetInstance()->SetTranslator(translator); + + // Check translation with custom translator + CHECK_STD_STRING(vtkMRMLI18N::Translate("SomeContext", "SomeMessage"), "translated-SomeContextSomeMessage"); + // Use translation convenience function + CHECK_STD_STRING(vtkMRMLTr("SomeContext", "SomeMessage"), "translated-SomeContextSomeMessage"); + + return EXIT_SUCCESS; +} diff --git a/Libs/MRML/Core/vtkMRMLI18N.cxx b/Libs/MRML/Core/vtkMRMLI18N.cxx new file mode 100644 index 00000000000..2105f289549 --- /dev/null +++ b/Libs/MRML/Core/vtkMRMLI18N.cxx @@ -0,0 +1,149 @@ +/*============================================================================== + + Program: 3D Slicer + + Copyright(c) Kitware Inc. + + See COPYRIGHT.txt + or http://www.slicer.org/copyright/copyright.txt for details. + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +==============================================================================*/ + +// MRML includes +#include "vtkMRMLI18N.h" +#include "vtkMRMLTranslator.h" + +// VTK includes +#include + +vtkCxxSetObjectMacro(vtkMRMLI18N, Translator, vtkMRMLTranslator); + +//---------------------------------------------------------------------------- +// The i18n manager singleton. +// This MUST be default initialized to zero by the compiler and is +// therefore not initialized here. The ClassInitialize and +// ClassFinalize methods handle this instance. +static vtkMRMLI18N* vtkMRMLI18NInstance; + +//---------------------------------------------------------------------------- +// Must NOT be initialized. Default initialization to zero is necessary. +unsigned int vtkMRMLI18NInitialize::Count; + +//---------------------------------------------------------------------------- +// Implementation of vtkMRMLI18NInitialize class. +//---------------------------------------------------------------------------- +vtkMRMLI18NInitialize::vtkMRMLI18NInitialize() +{ + if(++Self::Count == 1) + { + vtkMRMLI18N::classInitialize(); + } +} + +//---------------------------------------------------------------------------- +vtkMRMLI18NInitialize::~vtkMRMLI18NInitialize() +{ + if(--Self::Count == 0) + { + vtkMRMLI18N::classFinalize(); + } +} + +//---------------------------------------------------------------------------- + +//---------------------------------------------------------------------------- +// Up the reference count so it behaves like New +vtkMRMLI18N* vtkMRMLI18N::New() +{ + vtkMRMLI18N* ret = vtkMRMLI18N::GetInstance(); + ret->Register(nullptr); + return ret; +} + +//---------------------------------------------------------------------------- +// Return the single instance of the vtkMRMLI18N +vtkMRMLI18N* vtkMRMLI18N::GetInstance() +{ + if(!vtkMRMLI18NInstance) + { + // Try the factory first + vtkMRMLI18NInstance = (vtkMRMLI18N*)vtkObjectFactory::CreateInstance("vtkMRMLI18N"); + // if the factory did not provide one, then create it here + if(!vtkMRMLI18NInstance) + { + vtkMRMLI18NInstance = new vtkMRMLI18N; +#ifdef VTK_HAS_INITIALIZE_OBJECT_BASE + vtkMRMLI18NInstance->InitializeObjectBase(); +#endif + } + } + // return the instance + return vtkMRMLI18NInstance; +} + +//---------------------------------------------------------------------------- +vtkMRMLI18N::vtkMRMLI18N() +{ + this->Translator = nullptr; +} + +//---------------------------------------------------------------------------- +vtkMRMLI18N::~vtkMRMLI18N() +{ + if (this->Translator) + { + this->Translator->Delete(); + } + this->Translator = nullptr; +} + +//---------------------------------------------------------------------------- +void vtkMRMLI18N::PrintSelf(ostream& os, vtkIndent indent) +{ + this->vtkObject::PrintSelf(os, indent); + + os << indent << "Translator:"; + if (this->GetTranslator()) + { + this->GetTranslator()->PrintSelf(os, indent.GetNextIndent()); + } + else + { + os << " (none)" << "\n"; + } +} + +//---------------------------------------------------------------------------- +void vtkMRMLI18N::classInitialize() +{ + // Allocate the singleton + vtkMRMLI18NInstance = vtkMRMLI18N::GetInstance(); +} + +//---------------------------------------------------------------------------- +void vtkMRMLI18N::classFinalize() +{ + vtkMRMLI18NInstance->Delete(); + vtkMRMLI18NInstance = nullptr; +} + +//---------------------------------------------------------------------------- +std::string vtkMRMLI18N::Translate(const char *context, const char *sourceText, const char *disambiguation/*=nullptr*/, int n/*=-1*/) +{ + vtkMRMLI18N* i18n = vtkMRMLI18N::GetInstance(); + vtkMRMLTranslator* translator = i18n ? i18n->GetTranslator() : nullptr; + if (translator) + { + return translator->Translate(context, sourceText, disambiguation, n); + } + else + { + return sourceText ? sourceText : ""; + } +} diff --git a/Libs/MRML/Core/vtkMRMLI18N.h b/Libs/MRML/Core/vtkMRMLI18N.h new file mode 100644 index 00000000000..ae487195cda --- /dev/null +++ b/Libs/MRML/Core/vtkMRMLI18N.h @@ -0,0 +1,98 @@ +/*============================================================================== + + Program: 3D Slicer + + Copyright(c) Kitware Inc. + + See COPYRIGHT.txt + or http://www.slicer.org/copyright/copyright.txt for details. + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +==============================================================================*/ + +#ifndef __vtkMRMLI18N_h +#define __vtkMRMLI18N_h + +// MRML includes +#include "vtkMRML.h" + +// VTK includes +#include + +class vtkMRMLTranslator; + +/// \brief Class that provide internationalization (i18n) features, +/// such as language translation or region-specific units and date formatting. +/// +class VTK_MRML_EXPORT vtkMRMLI18N : public vtkObject +{ +public: + vtkTypeMacro(vtkMRMLI18N, vtkObject); + void PrintSelf(ostream& os, vtkIndent indent) override; + + /// + /// Return the singleton instance with no reference counting. + static vtkMRMLI18N* GetInstance(); + + /// + /// This is a singleton pattern New. There will only be ONE + /// reference to a vtkMRMLI18N object per process. Clients that + /// call this must call Delete on the object so that the reference + /// counting will work. The single instance will be unreferenced when + /// the program exits. + static vtkMRMLI18N* New(); + + /// Translate message with the current translator + static std::string Translate(const char *context, const char *sourceText, const char *disambiguation = nullptr, int n = -1); + + /// Set translator object. This class takes ownership of the translator + /// and it releases it when the process quits. + void SetTranslator(vtkMRMLTranslator* translator); + + /// Get translator object that can translate text that is displayed to the user + /// to the currently chosen language. + vtkGetObjectMacro (Translator, vtkMRMLTranslator); + +protected: + vtkMRMLI18N(); + ~vtkMRMLI18N() override; + vtkMRMLI18N(const vtkMRMLI18N&); + void operator=(const vtkMRMLI18N&); + + /// + /// Singleton management functions. + static void classInitialize(); + static void classFinalize(); + + friend class vtkMRMLI18NInitialize; + typedef vtkMRMLI18N Self; + + vtkMRMLTranslator* Translator{ nullptr }; +}; + +/// Utility class to make sure qSlicerModuleManager is initialized before it is used. +class VTK_MRML_EXPORT vtkMRMLI18NInitialize +{ +public: + typedef vtkMRMLI18NInitialize Self; + + vtkMRMLI18NInitialize(); + ~vtkMRMLI18NInitialize(); +private: + static unsigned int Count; +}; + +/// This instance will show up in any translation unit that uses +/// vtkMRMLI18N. It will make sure vtkMRMLI18N is initialized +/// before it is used. +static vtkMRMLI18NInitialize vtkMRMLI18NInitializer; + +/// Translation function used in MRML classes +#define vtkMRMLTr(context, sourceText) vtkMRMLI18N::Translate(context, sourceText) + +#endif diff --git a/Libs/MRML/Core/vtkMRMLTranslator.h b/Libs/MRML/Core/vtkMRMLTranslator.h new file mode 100644 index 00000000000..bc4bfc48c0b --- /dev/null +++ b/Libs/MRML/Core/vtkMRMLTranslator.h @@ -0,0 +1,49 @@ +/*============================================================================== + + Program: 3D Slicer + + Copyright(c) Kitware Inc. + + See COPYRIGHT.txt + or http://www.slicer.org/copyright/copyright.txt for details. + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +==============================================================================*/ + +#ifndef __vtkMRMLTranslator_h +#define __vtkMRMLTranslator_h + +// MRML includes +#include "vtkMRML.h" + +// VTK includes +#include + +// STD includes +#include + +/// \brief Base class for localization of messages that may be displayed to users. +/// +/// This base class keeps messages unchanged. Sub-classes must implement actual translation. +class VTK_MRML_EXPORT vtkMRMLTranslator : public vtkObject +{ +public: + vtkTypeMacro(vtkMRMLTranslator, vtkObject); + + /// Default translation function that returns the sourceText without any change. + /// This method must be overridden in derived classes. + virtual std::string Translate(const char *context, const char *sourceText, const char *disambiguation = nullptr, int n = -1) = 0; + +protected: + vtkMRMLTranslator() = default; + ~vtkMRMLTranslator() override = default; + vtkMRMLTranslator(const vtkMRMLTranslator&) = delete; + void operator=(const vtkMRMLTranslator&) = delete; +}; + +#endif