Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 26 additions & 37 deletions rebuild_lang.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,15 +33,22 @@
https://qualcoder.org/
"""

from lxml import etree
import os
from pathlib import Path
import polib
import subprocess
import sys
import polib
from lxml import etree
from typing import Dict, Any, List

SUPPORTED_LANGUAGES = ['de', 'en', 'es', 'fr', 'it', 'ja', 'pt', 'sv', 'zh']

project_root = os.path.dirname(os.path.abspath(__file__))
i18n_directory = os.path.join(project_root, "src", "qualcoder", "i18n")
# SUPPORTED_LANGUAGES = ['de', 'en', 'es', 'fr', 'it', 'ja', 'pt', 'ro', 'sv', 'zh']
supported_languages = []
for root, dirs, files in os.walk(i18n_directory):
for file in files:
if len(Path(file).stem) == 2 and Path(file).stem not in supported_languages:
supported_languages.append(Path(file).stem)

def extract_pot_file(directory, pot_filename):
""" Called by: update_translation_placeholders """
Expand Down Expand Up @@ -107,12 +114,14 @@ def update_qt_ts_files(lang_=None):
Run from QualCoder-master folder
Warning: pylupdate6 overrides ,but does not update, existing ts files.
Called by: update_translation_placeholders
Args:
lang : String es, fr, etc
"""

translation_files = [f"app_{lang}.ts" for lang in SUPPORTED_LANGUAGES]
translation_files = [f"{lang}.ts" for lang in supported_languages]

if lang_ is not None:
translation_files = [f for f in translation_files if f.startswith(f"app_{lang_}")]
translation_files = [f for f in translation_files if f.startswith(f"{lang_}")]

script_path = os.path.dirname(os.path.realpath(__file__))
gui_directory = os.path.join(script_path, "src", "qualcoder", "GUI")
Expand All @@ -134,12 +143,7 @@ def rel_for_pro(path):

ts_files = []
for t in translation_files:
if lang_ is not None:
ts_path = os.path.join(i18n_directory, t)
else:
lang_from_file = t.replace("app_", "").replace(".ts", "")
ts_path = os.path.join(script_path, "src", "qualcoder", "i18n", lang_from_file, t)
ts_files.append(rel_for_pro(ts_path))
ts_files.append(rel_for_pro(i18n_directory) + "/" + t)

text = "SOURCES = \\\n"
text += " \\\n".join(ui_files)
Expand All @@ -153,7 +157,9 @@ def rel_for_pro(path):
print("Created project.pro file")

# Compile ts files
print("pro_file_path", pro_file_path)
subprocess.call(f'pylupdate5 "{pro_file_path}"', shell=True)
# Error here: pylupdate5 error: Cannot save '../i18n'
print("Updated ts translation files")

# Delete old .ts files
Expand Down Expand Up @@ -181,19 +187,15 @@ def recompile_translation(language=None):
This is a user path environment variable """

project_root = os.path.dirname(os.path.abspath(__file__))

language_list = SUPPORTED_LANGUAGES
language_list = supported_languages
if language in language_list:
language_list = [language]

# GETTEXT TRANSLATION
# .po-files
po_dir = os.path.join(project_root, "src", "qualcoder", "i18n")
po_files = [os.path.join(po_dir, lang_, f'{lang_}.po') for lang_ in language_list]

po_files = [os.path.join(i18n_directory, f'{lang_}.po') for lang_ in language_list]
# .mo-files
mo_basedir = os.path.join(project_root, "src", "qualcoder", "i18n")
mo_files = [os.path.join(mo_basedir, lang_, 'LC_MESSAGES', f'{lang_}.mo') for lang_ in language_list]
mo_files = [os.path.join(i18n_directory, f'{lang_}.mo') for lang_ in language_list]

for po_file, mo_file in zip(po_files, mo_files):
if os.path.exists(po_file):
Expand All @@ -208,14 +210,12 @@ def recompile_translation(language=None):
print(f'Skipping "{mo_file}".')

# Qt TRANSLATIONS

# .ts-files
ts_dir = os.path.join(project_root, "src", "qualcoder", 'i18n')
ts_files = [os.path.join(ts_dir, f'app_{lang_}.ts') for lang_ in language_list]
ts_files = [os.path.join(ts_dir, f'{lang_}.ts') for lang_ in language_list]

# .qm-files
qm_basedir = os.path.join(project_root, "src", "qualcoder", "i18n")
qm_files = [os.path.join(qm_basedir, lang_, f'app_{lang_}.qm') for lang_ in language_list]
qm_files = [os.path.join(i18n_directory, f'{lang_}.qm') for lang_ in language_list]

for ts_file, qm_file in zip(ts_files, qm_files):
if os.path.exists(ts_file):
Expand All @@ -227,14 +227,6 @@ def recompile_translation(language=None):
print(f"{qm_file} has been updated.")
else:
print(f'Skipping "{qm_file}".')

# Update base_64_lang_helper.py
answer = input(f'Do you want to update "base_64_lang_helper.py"? (y/n)')
if answer == 'y':
os.chdir(os.path.join(project_root, "src", 'qualcoder', 'i18n'))
cmd = os.path.join(project_root, "src", 'qualcoder', 'i18n', 'create_lang_script_base64.py')
subprocess.call(cmd, shell=True)
print('Updated base_64_lang_helper.py')
print("Finished")


Expand Down Expand Up @@ -285,31 +277,28 @@ def analyze_translation_file(file_path: str, file_type: str) -> Dict[str, Any]:
"partial": obsolete,
"untranslated": untranslated,
})

except Exception as e:
stats["error"] = str(e)

return stats


def analyze_translation_status(language: str = None) -> str:
"""Analyze translation status for .po and .ts files and generate a LANGUAGES.md report."""
project_root = os.path.dirname(os.path.abspath(__file__))
languages = SUPPORTED_LANGUAGES
languages = supported_languages # fix this bit later
if language and language in languages:
languages = [language]

# Collect data for all languages
report_data: Dict[str, Dict[str, Dict[str, Any]]] = {}

for lang in languages:
report_data[lang] = {
"gettext": analyze_translation_file(
os.path.join(project_root, "src", "qualcoder", "i18n", f"{lang}.po"),
"po",
),
"qt": analyze_translation_file(
os.path.join(project_root, "src", "qualcoder", "GUI", f"app_{lang}.ts"),
os.path.join(project_root, "src", "qualcoder", "i18n", f"{lang}.ts"),
"ts",
),
}
Expand Down
132 changes: 41 additions & 91 deletions src/qualcoder/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
import logging
from logging.handlers import RotatingFileHandler
import os
from pathlib import Path
import platform
import shutil
import sys
Expand All @@ -56,7 +57,6 @@
from qualcoder.helpers import Message, ImportPlainTextCodes
from qualcoder.import_survey import DialogImportSurvey
from qualcoder.information import DialogInformation, menu_shortcuts_display, coding_shortcuts_display
from qualcoder.i18n.base64_lang_helper import *
from qualcoder.journals import DialogJournals
from qualcoder.manage_files import DialogManageFiles
from qualcoder.manage_links import DialogManageLinks
Expand Down Expand Up @@ -3201,9 +3201,11 @@ def gui():
qual_app = App()
settings, ai_models = qual_app.load_settings()
project_path = qual_app.get_most_recent_projectpath()
# Check Noto Sans installed - for general application
# Noto Sans - for general application
install_noto_sans()
QtGui.QFontDatabase.addApplicationFont(os.path.join(home, ".qualcoder", "NotoSans-Regular.ttf"))
# DroidSandMono - for wordcloud
install_droid_sans_mono()
stylesheet = qual_app.merge_settings_with_default_stylesheet(settings)
app.setStyleSheet(stylesheet)
if sys.platform != 'darwin':
Expand All @@ -3212,9 +3214,9 @@ def gui():
pm.loadFromData(QtCore.QByteArray.fromBase64(qualcoder32_icon), "png")
app.setWindowIcon(QtGui.QIcon(pm))

# Use two character language setting
lang = settings.get('language', 'en')
# Test for pyinstall data files
''' DELETE THIS COMMENTED SECTION SOON ?
locale_dir = os.path.join(path, 'i18n')
# Need to get the external data directory for PyInstaller
if getattr(sys, 'frozen', False) and hasattr(sys, '_MEIPASS'):
Expand All @@ -3223,51 +3225,26 @@ def gui():
locale_dir = os.path.join(locale_dir, 'i18n')
# locale_dir = os.path.join(locale_dir, lang)
# locale_dir = os.path.join(locale_dir, 'LC_MESSAGES')
# print("LISTDIR: ", os.listdir(locale_dir))
install_language(lang) # Install language files on every start, so updates are reflected
# getlang = gettext.translation('en', localedir=locale_dir, languages=['en'])
translator = gettext.translation(domain='default', localedir=locale_dir, fallback=True)
if lang in ["de", "es", "fr", "it", "ja", "pt", "sv", "zh"]:
# qt translator applies to ui designed GUI widgets only
# qt_locale_dir = os.path.join(locale_dir, lang)
# qt_locale_file = os.path.join(qt_locale_dir, "app_" + lang + ".qm")
# print("qt qm translation file: ", qt_locale_file)
# print("LISTDIR: ", os.listdir(locale_dir))'''
if lang != 'en':
install_language(lang)
# Qt translation for GUIs
qt_translator = QtCore.QTranslator()
# qt_translator.load(qt_locale_file)
''' Below for pyinstaller and obtaining app_lang.qm data file from .qualcoder folder
A solution to this error [Errno 13] Permission denied:
Replace 'lang' with the short language name, e.g. app_de.qm '''
if qt_translator.isEmpty():
print("trying to load translation qm file from .qualcoder folder")
qm = os.path.join(home, '.qualcoder')
qm = os.path.join(qm, f"app_{lang}.qm")
print("qm file located at: ", qm)
# print("trying to load translation qm file from .qualcoder folder")
qm = os.path.join(home, '.qualcoder', f'{lang}.qm')
qt_translator.load(qm)
'''if qt_translator.isEmpty():
print(f"Installing app_{lang}.qm to .qualcoder folder")
install_language(lang)
qt_translator.load(qm)'''
app.installTranslator(qt_translator)
'''Below for pyinstaller and obtaining mo data file from .qualcoder folder
A solution to this [Errno 13] Permission denied:
Must have the folder lang/LC_MESSAGES/lang.mo in the .qualcoder folder
Replace 'lang' with the language short name e.g. de, el, es ...
'''

# gettext translations for code strings
config_dir = os.path.join(home, '.qualcoder')
try:
translator = gettext.translation(lang, localedir=locale_dir, languages=[lang])
print("locale directory for python translations: ", locale_dir)
translator = gettext.translation(lang, localedir=config_dir, languages=[lang])
translator.install()
except Exception as err:
print("Error accessing python translations mo file\n", err)
print("Locale directory for python translations: ", locale_dir)
try:
print(f"Trying folder: home/.qualcoder/{lang}/LC_MESSAGES/{lang}.mo")
mo_dir = os.path.join(home, '.qualcoder')
translator = gettext.translation(lang, localedir=mo_dir, languages=[lang])
except Exception as err2:
print(f"No {lang}.mo translation file loaded", err2)
translator.install()
# Check DroidSandMono installed - for wordcloud
install_droid_sans_mono()
print(err)
logger.error(err)

ex = MainWindow(qual_app)
try:
if project_path:
Expand All @@ -3291,57 +3268,30 @@ def gui():


def install_language(lang):
""" Mainly for pyinstaller on Windows, as cannot access language data files.
So, recreate them from base64 data into home/.qualcoder folder.
Install Qt translation file into folder .qualcoder/app_lang.qm
Install poedit.mo file into folder .qualcoder/lang/LC_MESSAGES/lang.mo
""" Install Qt qm and .mo files into config folder .qualcoder.
Strict directory structure for gettext base dir/lang/LC_MESSAGES/lang.mo
args:
lang: String 2 letter code
"""

qm = os.path.join(home, '.qualcoder')
qm = os.path.join(qm, f'app_{lang}.qm')
qm_data = None
mo_data = None
if lang == "de":
qm_data = de_qm
mo_data = de_mo
if lang == "es":
qm_data = es_qm
mo_data = es_mo
if lang == "fr":
qm_data = fr_qm
mo_data = fr_mo
if lang == "it":
qm_data = it_qm
mo_data = it_mo
if lang == "ja":
qm_data = ja_qm
mo_data = ja_mo
if lang == "pt":
qm_data = pt_qm
mo_data = pt_mo
if lang == "sv":
qm_data = sv_qm
mo_data = sv_mo
if lang == "zh":
qm_data = zh_qm
mo_data = zh_mo
if qm_data is None or mo_data is None:
return
with open(qm, 'wb') as file_:
decoded_data = base64.decodebytes(qm_data)
file_.write(decoded_data)
mo_path = os.path.join(home, '.qualcoder')
mo_path = os.path.join(mo_path, lang)
if not os.path.exists(mo_path):
os.mkdir(mo_path)
mo_path = os.path.join(mo_path, "LC_MESSAGES")
if not os.path.exists(mo_path):
os.mkdir(mo_path)
mo = os.path.join(mo_path, lang + ".mo")
with open(mo, 'wb') as file_:
decoded_data = base64.decodebytes(mo_data)
file_.write(decoded_data)

config_path = os.path.join(home, '.qualcoder')
i18n_dir = os.path.join(Path(__file__).parent, 'i18n')
try:
shutil.copy(os.path.join(i18n_dir, f'{lang}.qm'), os.path.join(config_path, f'{lang}.qm'))
except Exception as err:
print(err)
if not os.path.exists(os.path.join(config_path, lang)):
os.mkdir(os.path.join(config_path, lang))
if not os.path.exists(os.path.join(config_path,lang, 'LC_MESSAGES')):
os.mkdir(os.path.join(config_path,lang, 'LC_MESSAGES'))
source = os.path.join(i18n_dir, f'{lang}.mo')
# Check source and if not present, check .qualcoder folder for an alternative source
if not os.path.exists(source):
source = os.path.join(config_path, f'{lang}.mo')
try:
shutil.copy(source, os.path.join(config_path,lang, 'LC_MESSAGES', f'{lang}.mo'))
except Exception as err:
print(err)

def install_droid_sans_mono():
""" Install DroidSansMono ttf font for wordclouds into .qualcoder folder """
Expand Down
57 changes: 0 additions & 57 deletions src/qualcoder/i18n/base64_lang_helper.py

This file was deleted.

Loading
Loading