Generate placeholder translations

Add placeholder_translations.py script to generate fake translations.
Run the script on GitHub Actions to add the translations to nightly
and development builds.

Fake translations are displayed in the UI like this:

    Source text:    Choose instruments
    Translation: ᵗʳ«Choose instruments»

This enables developers to see which strings have been correctly marked
for translation without having to wait for a proper translation to be
made available.

Special markup is used to identify plural translations:

    Source text:                       %n measure(s)
    Translation (1st plural form): ᵗʳ¹«%n measure(s)»
    Translation (2nd plural form): ᵗʳ²«%n measure(s)»
    Etc.

See the second page of the New Score dialog for an example of plurals.

Replacement markers %1, %2, etc. for QString arg() are also identified:

    Source text:     Add %1 to chord
    Translation: ᵗʳ«Add ⌜%1⌝ to chord»

Arguments must be translated separately to the main string:

    Non-translated argument:    ᵗʳ«Add ⌜D⌝ to chord»
    Translated argument:     ᵗʳ«Add ⌜ᵗʳ«D»⌝ to chord»

The Python script could be modified to display more information in the
translated strings such as context, disambiguation, comments or file
name and line number of the string in the code or in the .ts file.
This commit is contained in:
Peter Jonas 2022-07-27 02:01:52 +01:00
parent 558808a135
commit b612e18d96
8 changed files with 161 additions and 0 deletions

View file

@ -73,6 +73,13 @@ jobs:
fi
fi
DO_PLACEHOLDER_TRANSLATIONS='false'
if [[ "$DO_BUILD" == "true" ]]; then
if [[ "$BUILD_MODE" == "nightly_build" || "$BUILD_MODE" == "devel_build" ]]; then
DO_PLACEHOLDER_TRANSLATIONS='true'
fi
fi
DO_UPLOAD_SYMBOLS='false'
SENTRY_PROJECT=${{ github.event.inputs.sentry_project }}
SENTRY_URL=""
@ -101,6 +108,8 @@ jobs:
echo "DO_BUILD: $DO_BUILD"
echo "DO_UPDATE_TS=$DO_UPDATE_TS" >> $GITHUB_ENV
echo "DO_UPDATE_TS: $DO_UPDATE_TS"
echo "DO_PLACEHOLDER_TRANSLATIONS=$DO_PLACEHOLDER_TRANSLATIONS" >> $GITHUB_ENV
echo "DO_PLACEHOLDER_TRANSLATIONS: $DO_PLACEHOLDER_TRANSLATIONS"
echo "DO_PUBLISH=$DO_PUBLISH" >> $GITHUB_ENV
echo "DO_PUBLISH: $DO_PUBLISH"
echo "DO_UPLOAD_SYMBOLS=$DO_UPLOAD_SYMBOLS" >> $GITHUB_ENV
@ -116,11 +125,19 @@ jobs:
if: env.DO_BUILD == 'true'
run: |
sudo bash ./build/ci/linux/setup.sh
- name: Generate _en.ts files
if: env.DO_BUILD == 'true'
run: |
sudo bash ./build/ci/translation/run_lupdate.sh
- name: Update .ts files
if: env.DO_UPDATE_TS == 'true'
run: |
sudo bash ./build/ci/translation/tx_install.sh -t ${{ secrets.TRANSIFEX_API_TOKEN }} -s linux
sudo bash ./build/ci/translation/tx_pull.sh
- name: Generate placeholder.ts files
if: env.DO_PLACEHOLDER_TRANSLATIONS == 'true'
run: |
sudo python3 ./tools/translations/placeholder_translations.py
- name: Generate .qm files
if: env.DO_BUILD == 'true'
run: |

View file

@ -89,6 +89,13 @@ jobs:
fi
fi
DO_PLACEHOLDER_TRANSLATIONS='false'
if [[ "$DO_BUILD" == "true" ]]; then
if [[ "$BUILD_MODE" == "nightly_build" || "$BUILD_MODE" == "devel_build" ]]; then
DO_PLACEHOLDER_TRANSLATIONS='true'
fi
fi
DO_UPLOAD_SYMBOLS='false'
SENTRY_PROJECT=${{ github.event.inputs.sentry_project }}
SENTRY_URL=""
@ -118,6 +125,8 @@ jobs:
echo "DO_BUILD: $DO_BUILD"
echo "DO_UPDATE_TS=$DO_UPDATE_TS" >> $GITHUB_ENV
echo "DO_UPDATE_TS: $DO_UPDATE_TS"
echo "DO_PLACEHOLDER_TRANSLATIONS=$DO_PLACEHOLDER_TRANSLATIONS" >> $GITHUB_ENV
echo "DO_PLACEHOLDER_TRANSLATIONS: $DO_PLACEHOLDER_TRANSLATIONS"
echo "DO_NOTARIZE=$DO_NOTARIZE" >> $GITHUB_ENV
echo "DO_NOTARIZE: $DO_NOTARIZE"
echo "DO_PUBLISH=$DO_PUBLISH" >> $GITHUB_ENV
@ -135,11 +144,19 @@ jobs:
if: env.DO_BUILD == 'true'
run: |
bash ./build/ci/macos/setup.sh
- name: Generate _en.ts files
if: env.DO_BUILD == 'true'
run: |
bash ./build/ci/translation/run_lupdate.sh
- name: Update .ts files
if: env.DO_UPDATE_TS == 'true'
run: |
bash ./build/ci/translation/tx_install.sh -t ${{ secrets.TRANSIFEX_API_TOKEN }} -s macos
bash ./build/ci/translation/tx_pull.sh
- name: Generate placeholder.ts files
if: env.DO_PLACEHOLDER_TRANSLATIONS == 'true'
run: |
python3 ./tools/translations/placeholder_translations.py
- name: Generate .qm files
if: env.DO_BUILD == 'true'
run: |

View file

@ -69,6 +69,13 @@ jobs:
fi
fi
DO_PLACEHOLDER_TRANSLATIONS='false'
if [[ "$DO_BUILD" == "true" ]]; then
if [[ "$BUILD_MODE" == "nightly_build" || "$BUILD_MODE" == "devel_build" ]]; then
DO_PLACEHOLDER_TRANSLATIONS='true'
fi
fi
DO_UPLOAD_SYMBOLS='false'
SENTRY_PROJECT=${{ github.event.inputs.sentry_project }}
SENTRY_URL=""
@ -97,6 +104,8 @@ jobs:
echo "DO_BUILD: $DO_BUILD"
echo "DO_UPDATE_TS=$DO_UPDATE_TS" >> $GITHUB_ENV
echo "DO_UPDATE_TS: $DO_UPDATE_TS"
echo "DO_PLACEHOLDER_TRANSLATIONS=$DO_PLACEHOLDER_TRANSLATIONS" >> $GITHUB_ENV
echo "DO_PLACEHOLDER_TRANSLATIONS: $DO_PLACEHOLDER_TRANSLATIONS"
echo "DO_PUBLISH=$DO_PUBLISH" >> $GITHUB_ENV
echo "DO_PUBLISH: $DO_PUBLISH"
echo "DO_UPLOAD_SYMBOLS=$DO_UPLOAD_SYMBOLS" >> $GITHUB_ENV
@ -117,12 +126,22 @@ jobs:
shell: bash
run: |
bash ./build/ci/windows/make_environment.sh
- name: Generate _en.ts files
if: env.DO_BUILD == 'true'
shell: bash
run: |
bash ./build/ci/translation/run_lupdate.sh
- name: Update .ts files
if: env.DO_UPDATE_TS == 'true'
shell: bash
run: |
bash ./build/ci/translation/tx_install.sh -t ${{ secrets.TRANSIFEX_API_TOKEN }} -s windows
bash ./build/ci/translation/tx_pull.sh
- name: Generate placeholder.ts files
if: env.DO_PLACEHOLDER_TRANSLATIONS == 'true'
shell: bash
run: |
python3 -X utf8 ./tools/translations/placeholder_translations.py
- name: Generate .qm files
if: env.DO_BUILD == 'true'
shell: bash

1
.gitignore vendored
View file

@ -26,6 +26,7 @@ MuseScorePortable
/*.files
/*.includes
*.user
/share/locale/*placeholder.ts
*.qm
share/manual/en
share/manual/de

View file

@ -92,6 +92,13 @@ QVariantList GeneralPreferencesModel::languages() const
return l.toMap().value("code").toString() < r.toMap().value("code").toString();
});
if (!languagesConfiguration()->languageFilePaths(PLACEHOLDER_LANGUAGE_CODE).empty()) {
QVariantMap placeholderLanguageObj;
placeholderLanguageObj["code"] = PLACEHOLDER_LANGUAGE_CODE;
placeholderLanguageObj["name"] = "«Placeholder translations»";
result.prepend(placeholderLanguageObj);
}
QVariantMap systemLanguageObj;
systemLanguageObj["code"] = SYSTEM_LANGUAGE_CODE;
systemLanguageObj["name"] = mu::qtrc("appshell/preferences", "System default");

View file

@ -201,6 +201,14 @@ void LanguagesService::setCurrentLanguage(const QString& languageCode)
return;
}
if (languageCode == PLACEHOLDER_LANGUAGE_CODE) {
Ret load = loadLanguage(languageCode);
if (!load) {
LOGE() << load.toString();
}
return; // no hash for this language
}
LanguagesHash languageHash = languages().val;
if (!languageHash.contains(languageCode)) {
LOGE() << "Unknown language: " << languageCode;

View file

@ -31,6 +31,7 @@
namespace mu::languages {
const QString SYSTEM_LANGUAGE_CODE = "system";
const QString PLACEHOLDER_LANGUAGE_CODE = "en@placeholder";
class LanguageStatus
{

View file

@ -0,0 +1,91 @@
#!/usr/bin/env python3
# Generates fake translations to be displayed in MuseScore's UI when the
# language is set to «Placeholder translations» in Preferences > General.
# This enables developers to see which strings have been correctly marked for
# translation without having to wait for a proper translation to be made.
# Steps:
# 1. Add Qt's bin folder to $PATH
# 2. Call run_lupdate.sh
# 3. Run this script
# 4. Call run_lrelease.sh
# 5. Compile & run MuseScore
# 6. In Preferences > General, set language to «Placeholder translations»
import glob
import io
import os
import re
import sys
import xml.etree.ElementTree as ET
def eprint(*args, **kwargs):
print(*args, file=sys.stderr, **kwargs)
superscript_numbers = '¹²³⁴⁵⁶⁷⁸⁹'
os.chdir(sys.path[0] + '/../..') # make all paths relative to repository root
source_lang = 'en'
target_lang = 'en@placeholder' # Substring before '@' must correspond to a real
# locale for plurals to work. You must provide as
# many plural forms as are required by the locale.
source_lang_ts = source_lang + '.ts'
target_lang_ts = target_lang + '.ts'
for source_file in glob.glob('share/locale/*_' + source_lang_ts):
target_file = source_file[:-len(source_lang_ts)] + target_lang_ts
eprint("Reading " + source_file)
tree = ET.parse(source_file)
root = tree.getroot()
assert(root.tag == 'TS')
root.set('language', target_lang)
for message in root.findall('.//message'):
source = message.find('source')
translation = message.find('translation')
plurals = translation.findall('numerusform')
# use the source as basis for the translation
tr_txt = source.text
# identify QString arg() markers '%1', '%2', etc. as their
# arguments will require separate translation
tr_txt = re.sub(r'%([1-9]+)', r'%\1⌝', tr_txt)
# identify start and end of translated string
tr_txt = '«' + tr_txt + '»'
if plurals:
for idx, plural in enumerate(plurals):
plural.text = 'ᵗʳ' + superscript_numbers[idx] + tr_txt
else:
translation.text = 'ᵗʳ' + tr_txt
eprint("Writing " + target_file)
# Tweak ElementTree's XML formatting to match that of Qt's lrelease. The aim is to
# minimise the diff between source_file and target_file.
for el in root.findall('.//'):
if el.text:
# lrelease XML-escapes more characters than ElementTree, but ElementTree
# won't allow us to write the & escape character so use \r instead.
el.text = el.text.replace('\'', '\rapos;').replace('"', '\rquot;')
# Write XML tree to memory so that we can manipulate it as raw text
memfile = io.StringIO()
tree.write(memfile, encoding='unicode', xml_declaration=True)
# Manipulate raw XML and write to disk
with open(target_file, 'w', newline='\n', encoding='utf-8') as f:
memfile.seek(0)
f.write(next(iter(memfile))) # write first line (the XML declaration)
f.write('<!DOCTYPE TS>\n')
for line in memfile:
f.write(line
.replace('" />', '"/>') # remove space after final attribute in opening tags
.replace('\r', '&') # use the proper escape character in &apos; and &quot;
)