Commit 87c0182d authored by Friedrich W. H. Kossebau's avatar Friedrich W. H. Kossebau
Browse files

Support for translations with bundled apps, for now Marble Maps

Summary:
A new Script download-pos.sh allows to get marble_qt po files either
for trunk, stable or a tag, either all or a certain language.
Example: data/lang/download-pos.sh trunk de
To be used optionally with packaging or development builds.

Additionally a target "bundle_translations" is added which can
be explicitly invoked to generate qm files for the po files
fetched before with download-pos.sh

Also remove outdated scripts from qt4 times.

Reviewers: shentey, nienhueser, #marble

Reviewed By: nienhueser, #marble

Differential Revision: https://phabricator.kde.org/D3094
parent ef7f4291
SET (TARGET merge_ts_po)
PROJECT (${TARGET})
include_directories(
${CMAKE_CURRENT_SOURCE_DIR}
${CMAKE_CURRENT_BINARY_DIR}
)
set( ${TARGET}_SRC merge_ts_po.cpp )
add_executable( ${TARGET} ${${TARGET}_SRC} )
target_link_libraries( ${TARGET} Qt5::Core )
# MARBLE_WRAP_PO(qmfiles pofile1 pofile2 ... )
MACRO(MARBLE_WRAP_PO qmfiles)
FILE(GLOB_RECURSE all_sources RELATIVE "${CMAKE_SOURCE_DIR}/src/" "${CMAKE_SOURCE_DIR}/src/*.cpp" "${CMAKE_SOURCE_DIR}/src/*.h" "${CMAKE_SOURCE_DIR}/src/*.ui")
SET(tstemplate ${CMAKE_CURRENT_BINARY_DIR}/template.ts)
ADD_CUSTOM_COMMAND(OUTPUT ${tstemplate}
COMMAND ${QT_LUPDATE_EXECUTABLE}
ARGS ${all_sources} -ts ${tstemplate}
WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}/src"
)
FOREACH (pofile ${ARGN})
GET_FILENAME_COMPONENT(pofile ${pofile} ABSOLUTE)
GET_FILENAME_COMPONENT(basename ${pofile} NAME_WE)
SET(tsfile "${CMAKE_CURRENT_BINARY_DIR}/${basename}.ts")
SET(qmfile "${CMAKE_CURRENT_BINARY_DIR}/${basename}.qm")
ADD_CUSTOM_COMMAND(OUTPUT ${tsfile}
COMMAND ${CMAKE_CURRENT_BINARY_DIR}/merge_ts_po
ARGS ${tstemplate} ${pofile} > ${tsfile}
OUTPUT ${qmfile}
COMMAND ${QT_LRELEASE_EXECUTABLE}
ARGS -silent ${tsfile} -qm ${qmfile}
DEPENDS ${TARGET} ${tstemplate} ${pofile}
# custom translation catalog , for creation of bundled binary packages
# TODO: disable for builds in normal linux distri builds, to not confuse people
# Find Qt translation tools
find_package(Qt5LinguistTools CONFIG)
# enable target in any case
add_custom_target(bundle_translations)
if(NOT Qt5LinguistTools_FOUND)
return()
endif()
if(TARGET Qt5::lconvert)
set(lconvert_executable Qt5::lconvert)
else()
# Qt < 5.3.1 does not define Qt5::lconvert
get_target_property(lrelease_location Qt5::lrelease LOCATION)
get_filename_component(lrelease_path ${lrelease_location} PATH)
find_program(lconvert_executable
NAMES lconvert-qt5 lconvert
PATHS ${lrelease_path}
NO_DEFAULT_PATH
)
SET(${qmfiles} ${${qmfiles}} "${qmfile}")
ENDFOREACH(pofile)
ENDMACRO(MARBLE_WRAP_PO)
endif()
function(marble_process_po_files_as_qm lang po_file)
# Create commands to turn po files into qm files
get_filename_component(po_file ${po_file} ABSOLUTE)
get_filename_component(filename_base ${po_file} NAME_WE)
# Include ${lang} in build dir because we might be called multiple times
# with the same ${filename_base}
set(build_dir ${CMAKE_CURRENT_BINARY_DIR}/locale/${lang})
set(ts_file ${build_dir}/${filename_base}.ts)
set(qm_file ${build_dir}/${filename_base}.qm)
FILE(GLOB LANG_SRC "*.po")
file(MAKE_DIRECTORY ${build_dir})
# Create our translation files.
MARBLE_WRAP_PO(LANG_FILES ${LANG_SRC})
# lconvert from .po to .ts, then lrelease from .ts to .qm.
add_custom_command(OUTPUT ${qm_file}
COMMAND ${lconvert_executable}
ARGS -i ${po_file} -o ${ts_file} -target-language ${lang}
COMMAND Qt5::lrelease
ARGS -removeidentical -nounfinished -silent ${ts_file} -qm ${qm_file}
DEPENDS ${po_file}
)
install(
FILES ${qm_file}
DESTINATION ${data_dir}/locale/${lang}
OPTIONAL # if not build, ignore it
)
ADD_CUSTOM_TARGET(translations ALL
DEPENDS ${LANG_FILES}
)
set(target_name translation_${lang}_${filename_base})
add_custom_target(${target_name} DEPENDS ${qm_file})
add_dependencies(bundle_translations ${target_name})
endfunction()
install(FILES ${LANG_FILES} DESTINATION ${MARBLE_DATA_INSTALL_PATH}/lang)
file(GLOB po_files "po/*/*.po")
foreach(po_file ${po_files})
get_filename_component(po_dir ${po_file} DIRECTORY)
get_filename_component(lang ${po_dir} NAME)
marble_process_po_files_as_qm(${lang} ${po_file})
endforeach()
Handling translation catalogs for app bundles
=============================================
Usage
-----
A package build is done as usual, just with 2 extra steps.
# Provide marble sources, e.g. working copy of repo or tarball untarred
[...]
# EXTRA STEP: Add translation sources to the sources (here for stable):
cd <toplevel_sources>
data/lang/download-pos.sh stable
# Configure build, run cmake as usual:
cd <toplevel_build>
cmake [...]
# Build as usual
make
# EXTRA STEP: Build translations for bundle package optionally
make bundle_translations
# Install as usual
# If bundle_translations was called before, this will also install the qm files
make install
# Package as usual
[...]
Challenge
---------
Translation catalogs are not part of the Marble source repository, instead
are in a different repository.
So on packaging time the catalogs need to be added to the working copy, so
they can be processed as needed and become part of the package product.
The usual KDE source packaging scripts are fetching all po files belonging to
the repo and are storing them in the toplevel dir in a subfolder po/$lang/*.po.
Additionally either the buildsystem has some if-po/-exists-process-and-install-po-files
or the packaging script will inject such logic into the buildsystem.
When creating binary packages, this is often done directly from a checkout of the
sources and not from the released source tarball. So the step of fetching all po
files has to be done here as well as making sure the processing and installation is done.
Not all products are build for all platforms. So e.g. for Android only Marble Maps
will be created (separate packaging done for Behaim app, ignored for now). Right now
the apps only or also released as bundles (Maps, Behaim, MarbleQt) or the lib only need
the one single marble_qt catalog. The other (ki18n) catalogs are only used on platforms
where binary packaging is done by the distributions and also unbundled.
So supporting translations with binary packaging needs to work independently:
* separate script to download po files, in separate folder:
download-pos.sh
* separate target name for creating the qm files:
bundle_translations
#!/bin/bash
# A script to download Marble Qt translations from KDE's SVN.
#
# To be used before cmake is called on the sources.
#
# This program is free software licensed under the GNU LGPL. You can
# find a copy of this license in LICENSE.txt in the top directory of
# the source code.
#
# Copyright 2016 Friedrich W. H. Kossebau <kossebau@kde.org>
#
# TODO: support updates properly (e.g. removing no longer existing po files)
has_subdirs=true
case "$1" in
trunk)
echo "Downloading from trunk"
svn_path_prefix="svn://anonsvn.kde.org/home/kde/trunk/l10n-kf5"
;;
stable)
echo "Downloading from stable"
svn_path_prefix="svn://anonsvn.kde.org/home/kde/branches/stable/l10n-kf5"
;;
"")
echo "Syntax: $0 trunk|stable|\"KDE Applications tag\" [lang]"
exit 1
;;
*)
TAG=$1
echo "Downloading from KDE Applications tag $1"
svn_path_prefix="svn://anonsvn.kde.org/home/kde/tags/Applications/${TAG}/kde-l10n/5"
has_subdirs=false
;;
esac
if [ ! -e data/lang ]; then
echo "$0 needs to be invoked from the toplevel source dir."
exit 1
fi
workdir="$(mktemp -d)"
pofile="${workdir}//marble_qt.po"
if [ $# -gt 1 ] ; then
languages=$2
else
subdirs="${workdir}/subdirs"
if [ "$has_subdirs" = true ] ; then
svn -q export "${svn_path_prefix}/subdirs" ${subdirs}
else
# check if tag exists
if svn info ${svn_path_prefix} > /dev/null 2>&1 ; then
# tags don't have a subdirs file, so create one from the dirs present
svn list ${svn_path_prefix} | egrep "/$" | sed "s,/$,," > ${subdirs}
else
echo "There seems to be no tag $TAG."
exit 1
fi
fi
languages=$(cat ${subdirs})
fi
for i in ${languages}
do
svn -q export "${svn_path_prefix}/${i}/messages/kdeedu/marble_qt.po" ${pofile} > /dev/null 2>&1
# some languages might not have a catalog
if [ -e ${pofile} ]; then
chmod a-w ${pofile} # mark as not-to-be-edited
target_dir="data/lang/po/${i}"
mkdir -p ${target_dir}
mv -f ${pofile} ${target_dir}
echo "Downloaded for language $i"
fi
done
if [ -e data/lang/po ]; then
# TODO: think about some way to check if there are local modifications to prevent their loss
touch data/lang/po/WARNING_CHANGES_TO_PO_FILES_WILL_BE_LOST
# language files are only picked up at configuration time by a glob expression
# TODO: think about some way to trigger this automatically, by detecting the need and changing some file
echo "For picking up new languages, the CMake config needs to be redone, e.g. by calling: make rebuild_cache"
fi
#!/bin/bash
# A script to download Marble Qt translations from KDE's SVN.
#
# This program is free software licensed under the GNU LGPL. You can
# find a copy of this license in LICENSE.txt in the top directory of
# the source code.
#
# Copyright 2011 Dennis Nienhüser <nienhueser@kde.org>
# Copyright 2013 Bernhard Beschow <bbeschow@cs.tu-berlin.de>
#
set -e
workdir="$(mktemp -d)"
#prefix="svn://anonsvn.kde.org/home/kde/branches/stable/l10n-kde4/"
#TAG="4.7.0"
#prefix="svn://anonsvn.kde.org/home/kde/tags/KDE/${TAG}/l10n-kde4/"
# Translations can also be loaded from SVN trunk, uncomment below.
prefix="svn://anonsvn.kde.org/home/kde/trunk/l10n-kde4"
echo "Downloading translations, please wait. This may take some time..."
svn -q export "${prefix}/subdirs" "${workdir}/subdirs"
for i in $(cat "${workdir}/subdirs")
do
if svn -q export --force "${prefix}/${i}/messages/kdeedu/marble_qt.po" "${workdir}/marble_qt.po" 2>/dev/null
then
if svn -q export --force "${prefix}/${i}/messages/kdeedu/marble.po" "${workdir}/marble.po" 2>/dev/null
then
echo >> "${workdir}/marble_qt.po"
cat "${workdir}/marble.po" >> "${workdir}/marble_qt.po"
mv "${workdir}/marble_qt.po" marble-${i}.po
fi
fi
done
rm "${workdir}/marble.po"
rm "${workdir}/subdirs"
rmdir "${workdir}"
test -e CMakeLists.txt && touch CMakeLists.txt
echo "Done."
#!/bin/sh
TAG=$1
if [ "${TAG}" = "" ]; then
TAG=4.7.3
echo "Syntax: marble_i18n KDE-tag"
echo "E.g.: marble_i18n ${TAG}"
echo "-------------------------"
echo "Assuming ${TAG} as a KDE-tag version for now."
fi
svn export svn://anonsvn.kde.org/home/kde/tags/KDE/${TAG}/kde-l10n/subdirs
for i in $(cat subdirs)
do
echo "Processing Language $i"
svn export svn://anonsvn.kde.org/home/kde/tags/KDE/${TAG}/kde-l10n/${i}/messages/kdeedu/marble.po
perl -pi -e 's/^[[:space:]]*#.*$//g' marble.po
lconvert marble.po -o marble_${i}.qm
done
//
// This file is part of the Marble Virtual Globe.
//
// This program is free software licensed under the GNU LGPL. You can
// find a copy of this license in LICENSE.txt in the top directory of
// the source code.
//
// Copyright 2011 Dennis Nienhüser <nienhueser@kde.org>
//
#include <QString>
#include <QFileInfo>
#include <QTextStream>
#include <QDebug>
void usage( const QString &app )
{
qDebug() << "Usage: " << app << " input.ts input.po";
}
int main( int argc, char** argv )
{
if ( argc != 3 ) {
usage( argv[0] );
return 0;
}
QFileInfo const ts = QFileInfo( QString( argv[1] ) );
if ( !ts.exists() ) {
usage( argv[0] );
return 1;
}
QFileInfo const po = QFileInfo( QString( argv[2] ) );
if ( !po.exists() ) {
usage( argv[0] );
return 2;
}
QMap<QString,QString> translations;
// Open the .po file and build a map of translations
QFile poFile( po.absoluteFilePath() );
poFile.open( QFile::ReadOnly );
QTextStream poStream( &poFile );
poStream.setCodec( "UTF-8" );
poStream.setAutoDetectUnicode( true );
QString source;
bool ignore = false;
while( !poStream.atEnd() ) {
QString line = poStream.readLine();
if ( line.startsWith( QLatin1String( "#, fuzzy" ) ) ) {
ignore = true;
} else if ( line.startsWith( QLatin1String( "msgid " ) ) ) {
source = line.mid( 7, line.size() - 8 );
} else if ( !source.isEmpty() && line.startsWith( QLatin1String( "msgstr " ) ) ) {
if ( ignore ) {
ignore = false;
} else {
QString translation = line.mid( 8, line.size() - 9 );
source.replace( QLatin1Char( '&' ), QLatin1String( "&amp;" ) );
translation.replace( QLatin1Char( '&' ), QLatin1String( "&amp; ") );
source.replace( QLatin1Char( '<' ), QLatin1String( "&lt;" ) );
translation.replace( QLatin1Char( '<' ), QLatin1String( "&lt;" ) );
source.replace( QLatin1Char( '>' ), QLatin1String( "&gt;" ) );
translation.replace( QLatin1Char( '>' ), QLatin1String( "&gt;" ) );
if ( !translation.isEmpty() ) {
translations[source] = translation;
}
}
}
}
QTextStream console( stdout );
console.setCodec( "UTF-8" );
// Open the .ts file and replace source strings with translations
// The modified .to file is dumped to stdout
QFile tsFile( ts.absoluteFilePath() );
tsFile.open( QFile::ReadOnly );
QTextStream tsStream( &tsFile );
tsStream.setCodec( "UTF-8" );
tsStream.setAutoDetectUnicode( true );
source.clear();
while( !tsStream.atEnd() ) {
QString line = tsStream.readLine().trimmed();
if ( line.startsWith( QLatin1String( "<source>" ) ) ) {
source = line.mid( 8, line.size() - 17 );
console << line << "\n";
} else if ( !source.isEmpty() &&
line == "<translation type=\"unfinished\"></translation>" &&
translations.contains( source ) ) {
console << "<translation>" << translations[source] << "</translation>\n";
} else if ( !source.isEmpty() &&
line == "<translation type=\"unfinished\"></translation>" ) {
console << line << "\n";
} else {
console << line << "\n";
}
}
return 0;
}
......@@ -15,10 +15,59 @@
#include "declarative/MarbleDeclarativePlugin.h"
#include <MarbleGlobal.h>
#include "MarbleMaps.h"
#include "MarbleDirs.h"
#include "TextToSpeechClient.h"
using namespace Marble;
static bool loadTranslation(const QString &localeDirName, QApplication &app)
{
// TODO: check if any translations for Qt modules have to be loaded,
// as they need to be explicitely loaded as well by the Qt-using app
#ifdef Q_OS_ANDROID
// load translation file from bundled packaging installation
const QString fullPath = MarbleDirs::systemPath() + QLatin1String("/locale/") + localeDirName + QLatin1String("/marble_qt.qm");
#else
// load translation file from normal "KDE Applications" packaging installation
const QString subPath = QLatin1String("locale/") + localeDirName + QLatin1String("/LC_MESSAGES/marble_qt.qm");
const QString fullPath = QStandardPaths::locate(QStandardPaths::GenericDataLocation, subPath);
if (fullPath.isEmpty()) {
return false;
}
#endif
QTranslator* translator = new QTranslator(&app);
if (!translator->load(fullPath)) {
delete translator;
return false;
}
app.installTranslator(translator);
return true;
}
// load KDE translators system based translations
static void loadTranslations(QApplication &app)
{
// Quote from ecm_create_qm_loader created code:
// The way Qt translation system handles plural forms makes it necessary to
// have a translation file which contains only plural forms for `en`.
// That's why we load the `en` translation unconditionally, then load the
// translation for the current locale to overload it.
const QString en(QStringLiteral("en"));
loadTranslation(en, app);
QLocale locale = QLocale::system();
if (locale.name() != en) {
if (!loadTranslation(locale.name(), app)) {
loadTranslation(locale.bcp47Name(), app);
}
}
}
#ifdef Q_OS_ANDROID
// Declare symbol of main method as exported as needed by Qt-on-Android,
// where the Dalvik-native QtActivity class needs to find and invoke it
......@@ -35,6 +84,9 @@ int main(int argc, char ** argv)
app.setDesktopFileName(QStringLiteral("org.kde.marble.maps"));
#endif
// Load Qt translation system catalog for libmarblewidget, the plugins and this app
loadTranslations(app);
#ifdef Q_OS_ANDROID
MarbleGlobal::Profiles profiles = MarbleGlobal::SmallScreen | MarbleGlobal::HighResolution;
MarbleGlobal::getInstance()->setProfiles( profiles );
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment