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:
parent
558808a135
commit
b612e18d96
8 changed files with 161 additions and 0 deletions
17
.github/workflows/ci_linux_mu4.yml
vendored
17
.github/workflows/ci_linux_mu4.yml
vendored
|
@ -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: |
|
||||
|
|
17
.github/workflows/ci_macos_mu4.yml
vendored
17
.github/workflows/ci_macos_mu4.yml
vendored
|
@ -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: |
|
||||
|
|
19
.github/workflows/ci_windows_mu4.yml
vendored
19
.github/workflows/ci_windows_mu4.yml
vendored
|
@ -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
1
.gitignore
vendored
|
@ -26,6 +26,7 @@ MuseScorePortable
|
|||
/*.files
|
||||
/*.includes
|
||||
*.user
|
||||
/share/locale/*placeholder.ts
|
||||
*.qm
|
||||
share/manual/en
|
||||
share/manual/de
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -31,6 +31,7 @@
|
|||
|
||||
namespace mu::languages {
|
||||
const QString SYSTEM_LANGUAGE_CODE = "system";
|
||||
const QString PLACEHOLDER_LANGUAGE_CODE = "en@placeholder";
|
||||
|
||||
class LanguageStatus
|
||||
{
|
||||
|
|
91
tools/translations/placeholder_translations.py
Executable file
91
tools/translations/placeholder_translations.py
Executable 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 ' and "
|
||||
)
|
Loading…
Reference in a new issue