Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Translate user-visible text coming from logic classes #6647

Closed
lassoan opened this issue Nov 6, 2022 · 10 comments · Fixed by #7210
Closed

Translate user-visible text coming from logic classes #6647

lassoan opened this issue Nov 6, 2022 · 10 comments · Fixed by #7210
Labels
Domain: i18n Internationalization (language translation) Type: Enhancement Improvement to functionality
Milestone

Comments

@lassoan
Copy link
Contributor

lassoan commented Nov 6, 2022

Most text that is displayed for users is coming from Qt GUI classes and translated using Qt translation infrastructure. However, there are probably cases when the GUI displays messages coming from logic classes. We need to review all the module logics classes (e.g., in all Logic, MRML, MRMLDM, VTKWidgets folders) if they provide any strings that end up being displayed for users and make sure they are translated.

Design options:

  • Option A. Add a VTK-based translation class or function (that calls QApplication translate function). This is useful if there are many complex messages that logic classes may generate.
  • Option B. Translate messages coming from logic classes in the GUI classes that display them. This may be better if there are only few or simple cases when logic classes provide messages visible to the user.
@lassoan lassoan added Type: Enhancement Improvement to functionality Domain: i18n Internationalization (language translation) labels Nov 6, 2022
@lassoan lassoan added this to the Backlog milestone Nov 6, 2022
@lassoan
Copy link
Contributor Author

lassoan commented Jan 6, 2023

Examples:

  • Volume rendering preset names. The preset name comes from node names in the presets.xml scene file (see Improve volume rendering presets #6762 for some more details).
  • View labels (Red, Yellow, ...) and viewport names in layout XML files

@lassoan
Copy link
Contributor Author

lassoan commented Jan 10, 2023

A new MRML_TRANSLATE_NOOP macro could be added, which could work the same way as QT_TRANSLATE_NOOP.

#define QT_TRANSLATE_NOOP(scope, x) x

Source text for translation should be possible by specifying extra options for lupdate.

A global function or singleton class could be added to MRML that would call QCoreApplication::translate via a callback mechanism. The callback mechanism is needed to avoid MRML depend on Qt classes. The Qt application would set the callback function pointer to QCoreApplication::translate at application startup.

@lassoan
Copy link
Contributor Author

lassoan commented Feb 13, 2023

Instead of a macro, vtkMRMLTr method could be added that would be used like this in the source code:

this->SupportedReadFileTypes->InsertNextValue(vtkMRMLTr("vtkMRMLVolumePropertyStorageNode", "Volume Property")+" (.vp)");

lupdate can be configured to recognize this function by modifying how it is used in update_translations.py like this:

command = [lupdate_path, source_code_dir, "-tr-function-alias", "QT_TRANSLATE_NOOP+=vtkMRMLTr", "-locations", "absolute", "-ts", ts_file_path_in_source_tree]

@mhdiop
Copy link
Contributor

mhdiop commented Aug 10, 2023

Hi @lassoan,
Hi @pieper.

I've started working on the implementation of the vtkMRMLTr function in the Libs/MRML/Core/vtkMRML.h file.

I first wanted to use a function pointer as stated in the previous posts but the QCoreApplication::translate has optional parameters and such approach doesn't seem to be the most suitable one.

I then create a vtkMRMLTr function that has the same signature as QCoreApplication::translate and that directly calls it to make the translation. I tested it and it works.

However, strings present in the logic classes are not always of the same type. Many of them are std::string while others are char*. I can of course convert the QString returned by QCoreApplication::translate to any of those types but my question is which one should I choose as the return type of the translation function.

I know that any choice will have impact on the source code since it may result in many string conversions at translation function calls. Also, I wonder if those types are suitable for storing translated strings without any encoding issue ?

Here are some suggested implementations :

QString vtkMRMLTr(const char *context, const char *sourceText, const char *disambiguation = nullptr, int n = -1)
{
  return QCoreApplication::translate(context, sourceText, disambiguation, n);
}
std::string vtkMRMLTr(const char *context, const char *sourceText, const char *disambiguation = nullptr, int n = -1)
{
  return QCoreApplication::translate(context, sourceText, disambiguation, n).toStdString();
}

Regards.

@jcfr
Copy link
Member

jcfr commented Aug 10, 2023

Assuming we are focusing on translating strings from logic, what about adding virtual function vtkMRMLApplicationLocic::Translate .... then the call to QCoreApplication::translate would be done in a new class called qSlicerCoreApplicationLogic deriving from vtkSlicerApplicationLogic

@lassoan
Copy link
Contributor Author

lassoan commented Aug 10, 2023

Thank you for working on this. It seems like you are doing nice progress.

We cannot use any Qt functions or variables in vtkMRMLCore component. That's why we need the callback function. You have figured out the solution for default argument issue (create a function that has default arguments) you just need to call a function pointer from there (you cannot call QCoreApplication::translate(...) directly).

in vtkMRMLCore (Qt classes cannot be used):

vtkMRMLTranslateFunctionPtr = nullptr;

std::string vtkMRMLTr(const char *context, const char *sourceText, const char *disambiguation = nullptr, int n = -1)
{
    if (vtkMRMLTranslateFunctionPtr)
    {
        return (*vtkMRMLTranslateFunctionPtr)(context, sourceText, disambiguation, n);
    }
    else
    {
        return sourceText;
    }
}

in Slicer application (Qt classes can be used):

std::string SlicerAppMRMLTranslate(const char *context, const char *sourceText, const char *disambiguation, int n)
{
    return QCoreApplication::translate(context, sourceText, disambiguation, n).toStdString();
}

qSlicerApplicationPrivate::init()
{
    vtkMRMLTranslateFunctionPtr = SlicerAppMRMLTranslate;
}  

Using std::string return value of vtkMRMLTr is good because it avoids pointer ownership issues. Using const char* is safer than const std::string& because setting a null pointer as input will not crash the application.

It may be better to use namespaces or class names instead of polluting the global namespace with functions and function pointers.

@lassoan
Copy link
Contributor Author

lassoan commented Aug 10, 2023

I like @jcfr's suggestion of using virtual functions instead of global function pointers. It keeps the global namespace more tidy and syntax of virtual functions is much more readable than function pointers.

@pieper
Copy link
Member

pieper commented Aug 11, 2023

I agree with the virtual method approach. But I'd suggest that instead of creating a qSlicerCoreApplicationLogic class, I would think that we could have a vtkMRMLTranslator class with a virtual translate that is noop as an instance variable of vtkSlicerApplicationLogic with Get/Set methods. Then we could create a subclass qMRMLTranslator that could be set on the application's instance of vtkSlicerApplicationLogic. I think this would be easier to read and understand since the vtkSlicerApplicationLogic would remain a vtk class in all uses and the purpose of the translation code would be localized.

@jcfr
Copy link
Member

jcfr commented Aug 11, 2023

re: vtkMRMLTranslator + create a subclass qMRMLTranslator

Great idea. And this is consistent with the existing QTranslator1 class.

Footnotes

  1. https://doc.qt.io/qt-5/qtranslator.html

@lassoan
Copy link
Contributor Author

lassoan commented Aug 12, 2023

It could be inplemented the same way as vtkOutputWindow with static GetInstance/SetInstance methods.

lassoan pushed a commit to lassoan/Slicer that referenced this issue Sep 2, 2023
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 <mouhamed.diop@esp.sn>
lassoan pushed a commit that referenced this issue Sep 2, 2023
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
#6647
#6177

Co-authored-by: Mouhamed DIOP <mouhamed.diop@esp.sn>
mhdiop added a commit to mhdiop/Sliceriml that referenced this issue Apr 17, 2024
mhdiop added a commit to mhdiop/Sliceriml that referenced this issue Apr 30, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Domain: i18n Internationalization (language translation) Type: Enhancement Improvement to functionality
Development

Successfully merging a pull request may close this issue.

4 participants