Repository: mesonbuild/meson

Issue:
How to handle Qt5 translations?
In CMake there's a macro called `qt5_add_translation` which handles the conversion of `.ts` files to `.qm` files automatically (see eg [in sddm](https://github.com/sddm/sddm/blob/b78a49f03a265c73c41db31148d2e52f9c87604b/data/translations/CMakeLists.txt)).
I didn't find anything about how to handle translations properly in meson (except using GNU gettext).
Thanks.


Discussion:
I have created a custom target for getting it to work for now:
```
# Translations
languages = ['de']  # add new languages here!
translationsdir = join_paths(get_option('datadir'), 'razergenie/translations')
lrelease = find_program('lrelease')

foreach lang : languages
    custom_target('translation_' + lang,
        output : lang + '.qm',
        input : 'translations/'+lang+'.ts',
        command : [lrelease, '@INPUT@', '-qm', '@OUTPUT@'],
        install : true,
        install_dir : translationsdir)
endforeach
```
@z3ntu, thank you for reporting this. I'll make a PR to integrate this in Qt module.
Thanks!

Resolving patch:
diff --git a/mesonbuild/dependencies/ui.py b/mesonbuild/dependencies/ui.py
--- a/mesonbuild/dependencies/ui.py
+++ b/mesonbuild/dependencies/ui.py
@@ -207,13 +207,15 @@ def compilers_detect(self):
             moc = ExternalProgram(os.path.join(self.bindir, 'moc'), silent=True)
             uic = ExternalProgram(os.path.join(self.bindir, 'uic'), silent=True)
             rcc = ExternalProgram(os.path.join(self.bindir, 'rcc'), silent=True)
+            lrelease = ExternalProgram(os.path.join(self.bindir, 'lrelease'), silent=True)
         else:
             # We don't accept unsuffixed 'moc', 'uic', and 'rcc' because they
             # are sometimes older, or newer versions.
             moc = ExternalProgram('moc-' + self.name, silent=True)
             uic = ExternalProgram('uic-' + self.name, silent=True)
             rcc = ExternalProgram('rcc-' + self.name, silent=True)
-        return moc, uic, rcc
+            lrelease = ExternalProgram('lrelease-' + self.name, silent=True)
+        return moc, uic, rcc, lrelease
 
     def _pkgconfig_detect(self, mods, kwargs):
         # We set the value of required to False so that we can try the
diff --git a/mesonbuild/modules/qt.py b/mesonbuild/modules/qt.py
--- a/mesonbuild/modules/qt.py
+++ b/mesonbuild/modules/qt.py
@@ -43,13 +43,13 @@ def _detect_tools(self, env, method):
         kwargs = {'required': 'true', 'modules': 'Core', 'silent': 'true', 'method': method}
         qt = _QT_DEPS_LUT[self.qt_version](env, kwargs)
         # Get all tools and then make sure that they are the right version
-        self.moc, self.uic, self.rcc = qt.compilers_detect()
+        self.moc, self.uic, self.rcc, self.lrelease = qt.compilers_detect()
         # Moc, uic and rcc write their version strings to stderr.
         # Moc and rcc return a non-zero result when doing so.
         # What kind of an idiot thought that was a good idea?
-        for compiler, compiler_name in ((self.moc, "Moc"), (self.uic, "Uic"), (self.rcc, "Rcc")):
+        for compiler, compiler_name in ((self.moc, "Moc"), (self.uic, "Uic"), (self.rcc, "Rcc"), (self.lrelease, "lrelease")):
             if compiler.found():
-                stdout, stderr = Popen_safe(compiler.get_command() + ['-v'])[1:3]
+                stdout, stderr = Popen_safe(compiler.get_command() + ['-version'])[1:3]
                 stdout = stdout.strip()
                 stderr = stderr.strip()
                 if 'Qt {}'.format(self.qt_version) in stderr:
@@ -62,7 +62,7 @@ def _detect_tools(self, env, method):
                     raise MesonException('{name} preprocessor is not for Qt {version}. Output:\n{stdo}\n{stderr}'.format(
                         name=compiler_name, version=self.qt_version, stdo=stdout, stderr=stderr))
                 mlog.log(' {}:'.format(compiler_name.lower()), mlog.green('YES'), '({path}, {version})'.format(
-                    path=self.moc.get_path(), version=compiler_ver.split()[-1]))
+                    path=compiler.get_path(), version=compiler_ver.split()[-1]))
             else:
                 mlog.log(' {}:'.format(compiler_name.lower()), mlog.red('NO'))
         self.tools_detected = True
@@ -137,10 +137,28 @@ def preprocess(self, state, args, kwargs):
             moc_output = moc_gen.process_files('Qt{} moc header'.format(self.qt_version), moc_headers, state)
             sources.append(moc_output)
         if len(moc_sources) > 0:
-            arguments = moc_extra_arguments + ['@INPUT@', '-o', '@OUTPUT@']
+            arguments = moc_extra_arguments + inc + ['@INPUT@', '-o', '@OUTPUT@']
             moc_kwargs = {'output': '@BASENAME@.moc',
                           'arguments': arguments}
             moc_gen = build.Generator([self.moc], moc_kwargs)
             moc_output = moc_gen.process_files('Qt{} moc source'.format(self.qt_version), moc_sources, state)
             sources.append(moc_output)
         return ModuleReturnValue(sources, sources)
+
+    @permittedKwargs({'ts_files', 'install', 'install_dir', 'build_by_default', 'method'})
+    def compile_translations(self, state, args, kwargs):
+        ts_files, install_dir = extract_as_list(kwargs, 'ts_files', 'install_dir', pop=True)
+        self._detect_tools(state.environment, kwargs.get('method', 'auto'))
+        translations = []
+        for ts in ts_files:
+            cmd = [self.lrelease, '@INPUT@', '-qm', '@OUTPUT@']
+            lrelease_kwargs = {'output': '@BASENAME@.qm',
+                               'input': ts,
+                               'install': kwargs.get('install', False),
+                               'build_by_default': kwargs.get('build_by_default', False),
+                               'command': cmd}
+            if install_dir is not None:
+                lrelease_kwargs['install_dir'] = install_dir
+            lrelease_target = build.CustomTarget('qt{}-compile-{}'.format(self.qt_version, ts), state.subdir, state.subproject, lrelease_kwargs)
+            translations.append(lrelease_target)
+        return ModuleReturnValue(translations, translations)


Test patch:
diff --git a/test cases/frameworks/4 qt/meson.build b/test cases/frameworks/4 qt/meson.build
--- a/test cases/frameworks/4 qt/meson.build	
+++ b/test cases/frameworks/4 qt/meson.build	
@@ -59,6 +59,8 @@ foreach qt : ['qt4', 'qt5']
     # We need a console test application because some test environments
     # do not have an X server.
 
+    translations = qtmodule.compile_translations(ts_files : qt+'core_fr.ts', build_by_default : true)
+
     qtcore = dependency(qt, modules : 'Core', method : get_option('method'))
 
     qtcoreapp = executable(qt + 'core', 'q5core.cpp',
diff --git a/test cases/frameworks/4 qt/q5core.cpp b/test cases/frameworks/4 qt/q5core.cpp
--- a/test cases/frameworks/4 qt/q5core.cpp	
+++ b/test cases/frameworks/4 qt/q5core.cpp	
@@ -1,8 +1,26 @@
 #include <QCoreApplication>
+#include <QtGlobal>
+#include <QString>
+#include <QTranslator>
+#include <QLocale>
+#include <QLibraryInfo>
+#include <QDebug>
 
 int main(int argc, char **argv) {
   QCoreApplication app(argc, argv);
 
+  QTranslator qtTranslator;
+  qtTranslator.load("qt_" + QLocale::system().name(),
+                  QLibraryInfo::location(QLibraryInfo::TranslationsPath));
+  app.installTranslator(&qtTranslator);
+
+  QTranslator myappTranslator;
+  if(!myappTranslator.load("qt5core_fr") )
+    return 1;
+
+  app.installTranslator(&myappTranslator);
+
+  qDebug() << QObject::tr("Translate me!");
   // Don't actually start the main loop so this
   // can be run as a unit test.
   //return app.exec();
diff --git a/test cases/frameworks/4 qt/qt4core_fr.ts b/test cases/frameworks/4 qt/qt4core_fr.ts
new file mode 100644
--- /dev/null
+++ b/test cases/frameworks/4 qt/qt4core_fr.ts	
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE TS>
+<TS version="2.0" language="fr_FR">
+<context>
+    <name>QObject</name>
+    <message>
+        <location filename="q5core.cpp" line="23"/>
+        <source>Translate me!</source>
+        <translation>Traduisez moi!</translation>
+    </message>
+</context>
+</TS>
diff --git a/test cases/frameworks/4 qt/qt5core_fr.ts b/test cases/frameworks/4 qt/qt5core_fr.ts
new file mode 100644
--- /dev/null
+++ b/test cases/frameworks/4 qt/qt5core_fr.ts	
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE TS>
+<TS version="2.1" language="fr_FR">
+<context>
+    <name>QObject</name>
+    <message>
+        <location filename="q5core.cpp" line="23"/>
+        <source>Translate me!</source>
+        <translation>Traduisez moi!</translation>
+    </message>
+</context>
+</TS>