diff --git a/GetItemGroups.py b/GetItemGroups.py index 6e51a27..940e319 100644 --- a/GetItemGroups.py +++ b/GetItemGroups.py @@ -1,8 +1,14 @@ +import FreeCAD as App +import FreeCADGui as Gui + globalGroups = [] itemGroups = None serializedItemGroups = None +# Define the translation +translate = App.Qt.translate + def onResultSelected(index, groupId): global globalGroups @@ -17,8 +23,11 @@ def onResultSelected(index, groupId): QtGui.QMessageBox.warning( None, - "Could not execute this action", - "Could not execute this action, it could be from a Mod that has been uninstalled. Try refreshing the list of tools.", + translate("SearchBar", "Could not execute this action"), + translate( + "SearchBar", + "Could not execute this action, it could be from a Mod that has been uninstalled. Try refreshing the list of tools.", + ), ) @@ -31,7 +40,10 @@ def getToolTip(groupId, setParent): if handlerName in SearchResults.toolTipHandlers: return SearchResults.toolTipHandlers[handlerName](nfo, setParent) else: - return "Could not load tooltip for this tool, it could be from a Mod that has been uninstalled. Try refreshing the list of tools." + return translate( + "SearchBar", + "Could not load tooltip for this tool, it could be from a Mod that has been uninstalled. Try refreshing the list of tools.", + ) def getItemGroups(): diff --git a/InitGui.py b/InitGui.py index 295678e..f6c577b 100644 --- a/InitGui.py +++ b/InitGui.py @@ -1,9 +1,20 @@ +import FreeCAD as App +import FreeCADGui as Gui + # Avoid garbage collection by storing the action in a global variable wax = None sea = None tbr = None +def QT_TRANSLATE_NOOP(context, text): + return text + + +# Define the translation +translate = App.Qt.translate + + def addToolSearchBox(): import FreeCADGui from PySide import QtGui @@ -24,9 +35,13 @@ def addToolSearchBox(): if wax is None: wax = QtGui.QWidgetAction(None) - wax.setWhatsThis("Use this search bar to find tools, document objects, preferences and more") + wax.setWhatsThis( + translate("SearchBar", "Use this search bar to find tools, document objects, preferences and more") + ) - sea.setWhatsThis("Use this search bar to find tools, document objects, preferences and more") + sea.setWhatsThis( + translate("SearchBar", "Use this search bar to find tools, document objects, preferences and more") + ) wax.setDefaultWidget(sea) ##mbr.addWidget(sea) # mbr.addAction(wax) diff --git a/RefreshTools.py b/RefreshTools.py index b97887f..170d705 100644 --- a/RefreshTools.py +++ b/RefreshTools.py @@ -1,17 +1,20 @@ import os import FreeCAD as App +# Define the translation +translate = App.Qt.translate + def loadAllWorkbenches(): from PySide import QtGui import FreeCADGui activeWorkbench = FreeCADGui.activeWorkbench().name() - lbl = QtGui.QLabel("Loading workbench … (…/…)") + lbl = QtGui.QLabel(translate("SearchBar", "Loading workbench … (…/…)")) lbl.show() lst = FreeCADGui.listWorkbenches() for i, wb in enumerate(lst): - msg = "Loading workbench " + wb + " (" + str(i) + "/" + str(len(lst)) + ")" + msg = translate("SearchBar", "Loading workbench ") + wb + " (" + str(i) + "/" + str(len(lst)) + ")" print(msg) lbl.setText(msg) geo = lbl.geometry() @@ -88,8 +91,11 @@ def refreshToolsAction(): fw.clearFocus() reply = QtGui.QMessageBox.question( None, - "Load all workbenches?", - 'Load all workbenches? This can cause FreeCAD to become unstable, and this "reload tools" feature contained a bug that crashed freecad systematically, so please make sure you save your work before. It\'s a good idea to restart FreeCAD after this operation.', + translate("SearchBar", "Load all workbenches?"), + translate( + "SearchBar", + 'Load all workbenches? This can cause FreeCAD to become unstable, and this "reload tools" feature contained a bug that crashed freecad systematically, so please make sure you save your work before. It\'s a good idea to restart FreeCAD after this operation.', + ), QtGui.QMessageBox.Yes, QtGui.QMessageBox.No, ) diff --git a/ResultsDocument.py b/ResultsDocument.py index 2e0f8a1..41b6fa0 100644 --- a/ResultsDocument.py +++ b/ResultsDocument.py @@ -5,6 +5,9 @@ import FreeCADGui import SafeViewer import SearchBox +# Define the translation +translate = App.Qt.translate + def documentAction(nfo): act = nfo["action"] diff --git a/ResultsRefreshTools.py b/ResultsRefreshTools.py index d72d900..a4267f8 100644 --- a/ResultsRefreshTools.py +++ b/ResultsRefreshTools.py @@ -1,3 +1,6 @@ +import FreeCAD as App +import FreeCADGui as Gui + import os from PySide import QtGui import Serialize_SearchBar @@ -5,6 +8,9 @@ import Parameters_SearchBar as Parameters genericToolIcon = QtGui.QIcon(QtGui.QIcon(Parameters.genericToolIcon_Pixmap)) +# Define the translation +translate = App.Qt.translate + def refreshToolsAction(nfo): import RefreshTools @@ -15,7 +21,12 @@ def refreshToolsAction(nfo): def refreshToolsToolTip(nfo, setParent): return ( Serialize_SearchBar.iconToHTML(genericToolIcon) - + "

Load all workbenches to refresh this list of tools. This may take a minute, depending on the number of installed workbenches.

" + + "

" + + translate( + "SearchBar", + "Load all workbenches to refresh this list of tools. This may take a minute, depending on the number of installed workbenches.", + ) + + "

" ) diff --git a/ResultsToolbar.py b/ResultsToolbar.py index ec41db2..77967a4 100644 --- a/ResultsToolbar.py +++ b/ResultsToolbar.py @@ -1,7 +1,11 @@ +import FreeCAD as App from PySide import QtGui import FreeCADGui import Serialize_SearchBar +# Define the translation +translate = App.Qt.translate + def toolbarAction(nfo): act = nfo["action"] diff --git a/SafeViewer.py b/SafeViewer.py index 2832b95..dbf5a61 100644 --- a/SafeViewer.py +++ b/SafeViewer.py @@ -1,5 +1,8 @@ from PySide import QtGui -import FreeCAD +import FreeCAD as App + +# Define the translation +translate = App.Qt.translate class SafeViewer(QtGui.QWidget): @@ -7,7 +10,7 @@ class SafeViewer(QtGui.QWidget): FreeCAD's FreeCADGui.createViewer() puts the viewer widget inside an MDI window, and detaching it without causing segfaults on exit is tricky. This class contains some kludges to extract the viewer as a standalone widget and destroy it safely.""" - enabled = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/SearchBar").GetBool("PreviewEnabled", False) + enabled = App.ParamGet("User parameter:BaseApp/Preferences/Mod/SearchBar").GetBool("PreviewEnabled", False) instances = [] def __init__(self, parent=None): @@ -27,11 +30,18 @@ class SafeViewer(QtGui.QWidget): self.lbl_warning.setReadOnly(True) self.lbl_warning.setAlignment(QtCore.Qt.AlignTop) self.lbl_warning.setText( - "Warning: the 3D preview has some stability issues. It can cause FreeCAD to crash (usually when quitting the application) and could in theory cause data loss, inside and outside of FreeCAD." + translate( + "SearchBar", + "Warning: the 3D preview has some stability issues. It can cause FreeCAD to crash (usually when quitting the application) and could in theory cause data loss, inside and outside of App.", + ) + ) + self.btn_enable_for_this_session = QtGui.QPushButton( + translate("SearchBar", "Enable 3D preview for this session") ) - self.btn_enable_for_this_session = QtGui.QPushButton("Enable 3D preview for this session") self.btn_enable_for_this_session.clicked.connect(self.enable_for_this_session) - self.btn_enable_for_future_sessions = QtGui.QPushButton("Enable 3D preview for future sessions") + self.btn_enable_for_future_sessions = QtGui.QPushButton( + translate("SearchBar", "Enable 3D preview for future sessions") + ) self.btn_enable_for_future_sessions.clicked.connect(self.enable_for_future_sessions) self.setLayout(QtGui.QVBoxLayout()) self.layout().addWidget(self.lbl_warning) @@ -46,7 +56,7 @@ class SafeViewer(QtGui.QWidget): def enable_for_future_sessions(self): if not SafeViewer.enabled: # Store in prefs - FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/SearchBar").SetBool("PreviewEnabled", True) + App.ParamGet("User parameter:BaseApp/Preferences/Mod/SearchBar").SetBool("PreviewEnabled", True) # Then enable as usual self.enable_for_this_session() diff --git a/SearchBox.py b/SearchBox.py index 3a81a68..45fd0be 100644 --- a/SearchBox.py +++ b/SearchBox.py @@ -1,4 +1,5 @@ -import FreeCADGui as Gui # just used for FreeCADGui.updateGui() +import FreeCAD as App +import FreeCADGui as Gui import os from PySide.QtCore import ( @@ -44,6 +45,9 @@ genericToolIcon = QIcon(Parameters.genericToolIcon_Pixmap) globalIgnoreFocusOut = False +# Define the translation +translate = App.Qt.translate + def easyToolTipWidget(html): foo = QTextEdit() @@ -170,7 +174,7 @@ class SearchBox(QLineEdit): mdl = QStandardItemModel() mdl.appendRow( [ - QStandardItem(genericToolIcon, "Please wait, loading results from cache…"), + QStandardItem(genericToolIcon, translate("SearchBar", "Please wait, loading results from cache…")), QStandardItem("0"), QStandardItem("-1"), ] diff --git a/translations/README.md b/translations/README.md new file mode 100644 index 0000000..ae84e37 --- /dev/null +++ b/translations/README.md @@ -0,0 +1,104 @@ +# About translating Ribbon Addon + +> [!NOTE] +> All commands **must** be run in `./translations/` directory. + +> [!IMPORTANT] +> If you want to update/release the files you need to have installed +> `lupdate` and `lrelease` from Qt6 version. Using the versions from +> Qt5 is not advised because they're buggy. + +## Updating translations template file + +To update the template file from source files you should use this command: + +```shell +./update_translation.sh -U +``` + +Once done you can commit the changes and upload the new file to CrowdIn platform +at webpage and find the **Ribbon** project. + +## Creating file for missing locale + +### Using script + +To create a file for a new language with all **Ribbon** translatable strings execute +the script with `-u` flag plus your locale: + +```shell +./update_translation.sh -u de +``` + +### Renaming file + +Also you can rename new `SearchBar.ts` file by appending the locale code, +for example, `SearchBar_de.ts` for German and change + +```xml + +``` + +to + +```xml + +``` + +As of 2024/10/28 the supported locales on FreeCAD +(according to `FreeCADGui.supportedLocales()`) are 44: + +```python +{'English': 'en', 'Afrikaans': 'af', 'Arabic': 'ar', 'Basque': 'eu', +'Belarusian': 'be', 'Bulgarian': 'bg', 'Catalan': 'ca', +'Chinese Simplified': 'zh-CN', 'Chinese Traditional': 'zh-TW', 'Croatian': 'hr', +'Czech': 'cs', 'Danish': 'da', 'Dutch': 'nl', 'Filipino': 'fil', 'Finnish': 'fi', + 'French': 'fr', 'Galician': 'gl', 'Georgian': 'ka', 'German': 'de', 'Greek': 'el', + 'Hungarian': 'hu', 'Indonesian': 'id', 'Italian': 'it', 'Japanese': 'ja', + 'Kabyle': 'kab', 'Korean': 'ko', 'Lithuanian': 'lt', 'Norwegian': 'no', + 'Polish': 'pl', 'Portuguese': 'pt-PT', 'Portuguese, Brazilian': 'pt-BR', + 'Romanian': 'ro', 'Russian': 'ru', 'Serbian': 'sr', 'Serbian, Latin': 'sr-CS', + 'Slovak': 'sk', 'Slovenian': 'sl', 'Spanish': 'es-ES', 'Spanish, Argentina': 'es-AR', +'Swedish': 'sv-SE', 'Turkish': 'tr', 'Ukrainian': 'uk', 'Valencian': 'val-ES', +'Vietnamese': 'vi'} +``` + +## Translating + +To edit your language file open your file in `Qt Linguist` from `qt5-tools`/`qt6-tools` +package or in a text editor like `xed`, `mousepad`, `gedit`, `nano`, `vim`/`nvim`, +`geany` etc. and translate it. + +Alternatively you can visit the **FreeCAD-addons** project on CrowdIn platform +at webpage and find your language, +once done, look for the **Ribbon** project. + +## Compiling translations + +To convert all `.ts` files to `.qm` files (merge) you can use this command: + +```shell +./update_translation.sh -R +``` + +If you are a translator that wants to update only their language file +to test it on **FreeCAD** before doing a PR you can use this command: + +```shell +./update_translation.sh -r de +``` + +This will update the `.qm` file for your language (German in this case). + +## Sending translations + +Now you can contribute your translated `.ts` file to **Ribbon** repository, +also include the `.qm` file. + + + +## More information + +You can read more about translating external workbenches here: + + diff --git a/translations/update_translation.sh b/translations/update_translation.sh new file mode 100644 index 0000000..1422655 --- /dev/null +++ b/translations/update_translation.sh @@ -0,0 +1,166 @@ +#!/usr/bin/env bash + +# -------------------------------------------------------------------------------------------------- +# +# Create, update and release translation files. +# +# Supported locales on FreeCAD <2024-10-14, FreeCADGui.supportedLocales(), total=44>: +# {'English': 'en', 'Afrikaans': 'af', 'Arabic': 'ar', 'Basque': 'eu', 'Belarusian': 'be', +# 'Bulgarian': 'bg', 'Catalan': 'ca', 'Chinese Simplified': 'zh-CN', +# 'Chinese Traditional': 'zh-TW', 'Croatian': 'hr', 'Czech': 'cs', 'Danish': 'da', +# 'Dutch': 'nl', 'Filipino': 'fil', 'Finnish': 'fi', 'French': 'fr', 'Galician': 'gl', +# 'Georgian': 'ka', 'German': 'de', 'Greek': 'el', 'Hungarian': 'hu', 'Indonesian': 'id', +# 'Italian': 'it', 'Japanese': 'ja', 'Kabyle': 'kab', 'Korean': 'ko', 'Lithuanian': 'lt', +# 'Norwegian': 'no', 'Polish': 'pl', 'Portuguese': 'pt-PT', 'Portuguese, Brazilian': 'pt-BR', +# 'Romanian': 'ro', 'Russian': 'ru', 'Serbian': 'sr', 'Serbian, Latin': 'sr-CS', 'Slovak': 'sk', +# 'Slovenian': 'sl', 'Spanish': 'es-ES', 'Spanish, Argentina': 'es-AR', 'Swedish': 'sv-SE', +# 'Turkish': 'tr', 'Ukrainian': 'uk', 'Valencian': 'val-ES', 'Vietnamese': 'vi'} +# +# NOTE: PREPARATION +# - Install Qt tools +# Debian-based (e.g., Ubuntu): $ sudo apt-get install qttools5-dev-tools pyqt6-dev-tools +# Fedora-based: $ sudo dnf install qt6-linguist qt6-devel +# Arch-based: $ sudo pacman -S qt6-tools python-pyqt6 +# - Make the script executable +# $ chmod +x update_translation.sh +# - The script has to be executed within the `translations` directory. +# Executing the script with no flags invokes the help. +# $ ./update_translation.sh +# +# NOTE: WORKFLOW TRANSLATOR (LOCAL) +# - Execute the script passing the `-u` flag plus locale code as argument +# Only update the file(s) you're translating! +# $ ./update_translation.sh -u es-ES +# - Do the translation via Qt Linguist and use `File>Release` +# - If releasing with the script execute it passing the `-r` flag +# plus locale code as argument +# $ ./update_translation.sh -r es-ES +# +# NOTE: WORKFLOW MAINTAINER (CROWDIN) +# - Execute the script passing the '-U' flag +# $ ./update_translation.sh -U +# - Once done, download the translated files, copy them to `translations` +# - Upload the updated file to CrowdIn and wait for translators do their thing ;-) +# and release all the files to update the changes +# $ ./update_translation.sh -R +# +# -------------------------------------------------------------------------------------------------- + +supported_locales=( + "en" "af" "ar" "eu" "be" "bg" "ca" "zh-CN" "zh-TW" "hr" + "cs" "da" "nl" "fil" "fi" "fr" "gl" "ka" "de" "el" + "hu" "id" "it" "ja" "kab" "ko" "lt" "no" "pl" "pt-PT" + "pt-BR" "ro" "ru" "sr" "sr-CS" "sk" "sl" "es-ES" "es-AR" "sv-SE" + "tr" "uk" "val-ES" "vi" +) + +is_locale_supported() { + local locale="$1" + for supported_locale in "${supported_locales[@]}"; do + [ "$supported_locale" == "$locale" ] && return 0 + done + return 1 +} + +update_locale() { + local locale="$1" + local u=${locale:+_} # Conditional underscore + FILES="../*.py ../Resources/ui/*.ui" + + # NOTE: Execute the right commands depending on: + # - if it's a locale file or the main, agnostic one + [ ! -f "${WB}${u}${locale}.ts" ] && action="Creating" || action="Updating" + echo -e "\033[1;34m\n\t<<< ${action} '${WB}${u}${locale}.ts' file >>>\n\033[m" + if [ "$u" == "" ]; then + eval $LUPDATE "$FILES" -ts "${WB}.ts" # locale-agnostic file + else + eval $LUPDATE "$FILES" -source-language en_US -target-language "${locale//-/_}" \ + -ts "${WB}_${locale}.ts" + fi +} + +normalize_crowdin_files() { + # Rename files which locales are different on FreeCAD and delete not supported locales + crowdin_fixes=(af-ZA ar-SA be-BY bg-BG ca-ES cs-CZ da-DK de-DE el-GR eu-ES fi-FI + fil-PH fr-FR gl-ES hr-HR hu-HU it-IT ja-JP ka-GE kab-KAB ko-KR lt-LT nl-NL + no-NO pl-PL ro-RO ru-RU sk-SK sl-SI sr-SP tr-TR uk-UA vi-VN) + + crowdin_deletes=(az-AZ bn-BD br-FR bs-BA en en-GB en-US eo-UY es-CO es-VE et-EE fa-IR he-IL + hi-IN hy-AM id-ID kaa lv-LV mk-MK ms-MY sat-IN si-LK ta-IN te-IN th-TH ur-PK xav yo-NG) + + for pattern in "${crowdin_fixes[@]}"; do + find . -type f -name "*_${pattern}\.*" | while read -r file; do + mv -v "$file" "${file//-*./.}" + done + done + + for pattern in "${crowdin_deletes[@]}"; do + find . -type f -name "*_${pattern}\.*" -delete + done +} + +help() { + echo -e "\nDescription:" + echo -e "\tCreate, update and release translation files." + echo -e "\nUsage:" + echo -e "\t./update_translation.sh [-R] [-U] [-r ] [-u ]" + echo -e "\nFlags:" + echo -e " -R\n\tRelease all translations (qm files)" + echo -e " -U\n\tUpdate all translations (ts files)" + echo -e " -r \n\tRelease the specified locale" + echo -e " -u \n\tUpdate strings for the specified locale" + echo -e " -N\n\tNormalize CrowdIn filenames" +} + +# Main function ------------------------------------------------------------------------------------ + +LUPDATE=/usr/lib/qt6/bin/lupdate # from Qt6 +# LUPDATE=lupdate # from Qt5 +LRELEASE=/usr/lib/qt6/bin/lrelease # from Qt6 +# LRELEASE=lrelease # from Qt5 +WB="SearchBar" + +sed -i '3s/-/_/' ${WB}*.ts # Enforce underscore on locales +sed -i '3s/\"en\"/\"en_US\"/g' ${WB}*.ts # Use en_US + +if [ $# -eq 1 ]; then + if [ "$1" == "-R" ]; then + find . -type f -name '*_*.ts' | while IFS= read -r file; do + # Release all locales + $LRELEASE -nounfinished "$file" + echo + done + elif [ "$1" == "-U" ]; then + for locale in "${supported_locales[@]}"; do + update_locale "$locale" + done + elif [ "$1" == "-u" ]; then + update_locale # Update main file (agnostic) + elif [ "$1" == "-N" ]; then + normalize_crowdin_files + else + help + fi +elif [ $# -eq 2 ]; then + LOCALE="$2" + if is_locale_supported "$LOCALE"; then + if [ "$1" == "-r" ]; then + # Release locale (creation of *.qm file from *.ts file) + $LRELEASE -nounfinished "${WB}_${LOCALE}.ts" + elif [ "$1" == "-u" ]; then + # Update main & locale files + update_locale + update_locale "$LOCALE" + fi + else + echo "Verify your language code. Case sensitive." + echo "If it's correct, ask a maintainer to add support for your language on FreeCAD." + echo -e "\nSupported locales, '\033[1;34mFreeCADGui.supportedLocales()\033[m': \033[1;33m" + for locale in $(printf "%s\n" "${supported_locales[@]}" | sort); do + echo -n "$locale " + done + echo + fi +else + help +fi