Files
create/src/Mod/AddonManager/package_list.py
Chris Hennes c62dc32739 Addon Manager: Translation cleanup
pylupdate does not extract translations when f-strings are used for the
translated text, so all f-strings are migrated to calls to format().
Several other minor translation issues are also addressed.

NOTE: This code has been run through the Black reformatter, which adds
trailing commas in many places that the stock Qt 5.x pylupdate does not
recognize. This code must be processed with the corrected pylupdate to
generate the correct translations.
2022-01-27 23:11:31 -06:00

711 lines
28 KiB
Python

# -*- coding: utf-8 -*-
# ***************************************************************************
# * *
# * Copyright (c) 2021 Chris Hennes <chennes@pioneerlibrarysystem.org> *
# * *
# * This program is free software; you can redistribute it and/or modify *
# * it under the terms of the GNU Lesser General Public License (LGPL) *
# * as published by the Free Software Foundation; either version 2 of *
# * the License, or (at your option) any later version. *
# * for detail see the LICENCE text file. *
# * *
# * This program is distributed in the hope that it will be useful, *
# * but WITHOUT ANY WARRANTY; without even the implied warranty of *
# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
# * GNU Library General Public License for more details. *
# * *
# * You should have received a copy of the GNU Library General Public *
# * License along with this program; if not, write to the Free Software *
# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 *
# * USA *
# * *
# ***************************************************************************
import FreeCAD
from PySide2.QtCore import *
from PySide2.QtGui import *
from PySide2.QtWidgets import *
from enum import IntEnum
import threading
from AddonManagerRepo import AddonManagerRepo
from compact_view import Ui_CompactView
from expanded_view import Ui_ExpandedView
translate = FreeCAD.Qt.translate
class ListDisplayStyle(IntEnum):
COMPACT = 0
EXPANDED = 1
class StatusFilter(IntEnum):
ANY = 0
INSTALLED = 1
NOT_INSTALLED = 2
UPDATE_AVAILABLE = 3
class PackageList(QWidget):
"""A widget that shows a list of packages and various widgets to control the display of the list"""
itemSelected = Signal(AddonManagerRepo)
def __init__(self, parent=None):
super().__init__(parent)
self.ui = Ui_PackageList()
self.ui.setupUi(self)
self.item_filter = PackageListFilter()
self.ui.listPackages.setModel(self.item_filter)
self.item_delegate = PackageListItemDelegate(self.ui.listPackages)
self.ui.listPackages.setItemDelegate(self.item_delegate)
self.ui.listPackages.clicked.connect(self.on_listPackages_clicked)
self.ui.comboPackageType.currentIndexChanged.connect(self.update_type_filter)
self.ui.comboStatus.currentIndexChanged.connect(self.update_status_filter)
self.ui.lineEditFilter.textChanged.connect(self.update_text_filter)
self.ui.buttonCompactLayout.clicked.connect(
lambda: self.set_view_style(ListDisplayStyle.COMPACT)
)
self.ui.buttonExpandedLayout.clicked.connect(
lambda: self.set_view_style(ListDisplayStyle.EXPANDED)
)
# Only shows when the user types in a filter
self.ui.labelFilterValidity.hide()
# Set up the view the same as the last time:
pref = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Addons")
package_type = pref.GetInt("PackageTypeSelection", 1)
self.ui.comboPackageType.setCurrentIndex(package_type)
status = pref.GetInt("StatusSelection", 0)
self.ui.comboStatus.setCurrentIndex(status)
def setModel(self, model):
self.item_model = model
self.item_filter.setSourceModel(self.item_model)
self.item_filter.sort(0)
pref = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Addons")
style = pref.GetInt("ViewStyle", ListDisplayStyle.EXPANDED)
self.set_view_style(style)
if style == ListDisplayStyle.EXPANDED:
self.ui.buttonExpandedLayout.setChecked(True)
else:
self.ui.buttonCompactLayout.setChecked(True)
self.item_filter.setHidePy2(pref.GetBool("HidePy2", True))
self.item_filter.setHideObsolete(pref.GetBool("HideObsolete", True))
def on_listPackages_clicked(self, index: QModelIndex):
source_selection = self.item_filter.mapToSource(index)
selected_repo = self.item_model.repos[source_selection.row()]
self.itemSelected.emit(selected_repo)
def update_type_filter(self, type_filter: int) -> None:
"""hide/show rows corresponding to the type filter
type_filter is an integer: 0 for all, 1 for workbenches, 2 for macros, and 3 for preference packs
"""
self.item_filter.setPackageFilter(type_filter)
pref = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Addons")
pref.SetInt("PackageTypeSelection", type_filter)
def update_status_filter(self, status_filter: int) -> None:
"""hide/show rows corresponding to the status filter
status_filter is an integer: 0 for any, 1 for installed, 2 for not installed, and 3 for update available
"""
self.item_filter.setStatusFilter(status_filter)
pref = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Addons")
pref.SetInt("StatusSelection", status_filter)
def update_text_filter(self, text_filter: str) -> None:
"""filter name and description by the regex specified by text_filter"""
if text_filter:
if hasattr(
self.item_filter, "setFilterRegularExpression"
): # Added in Qt 5.12
test_regex = QRegularExpression(text_filter)
else:
test_regex = QRegExp(text_filter)
if test_regex.isValid():
self.ui.labelFilterValidity.setToolTip(
translate("AddonsInstaller", "Filter is valid")
)
icon = QIcon.fromTheme("ok", QIcon(":/icons/edit_OK.svg"))
self.ui.labelFilterValidity.setPixmap(icon.pixmap(16, 16))
else:
self.ui.labelFilterValidity.setToolTip(
translate("AddonsInstaller", "Filter regular expression is invalid")
)
icon = QIcon.fromTheme("cancel", QIcon(":/icons/edit_Cancel.svg"))
self.ui.labelFilterValidity.setPixmap(icon.pixmap(16, 16))
self.ui.labelFilterValidity.show()
else:
self.ui.labelFilterValidity.hide()
if hasattr(self.item_filter, "setFilterRegularExpression"): # Added in Qt 5.12
self.item_filter.setFilterRegularExpression(text_filter)
else:
self.item_filter.setFilterRegExp(text_filter)
def set_view_style(self, style: ListDisplayStyle) -> None:
self.item_model.layoutAboutToBeChanged.emit()
self.item_delegate.set_view(style)
if style == ListDisplayStyle.COMPACT:
self.ui.listPackages.setSpacing(2)
else:
self.ui.listPackages.setSpacing(5)
self.item_model.layoutChanged.emit()
pref = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Addons")
pref.SetInt("ViewStyle", style)
class PackageListItemModel(QAbstractListModel):
repos = []
write_lock = threading.Lock()
DataAccessRole = Qt.UserRole
StatusUpdateRole = Qt.UserRole + 1
IconUpdateRole = Qt.UserRole + 2
def __init__(self, parent=None) -> None:
super().__init__(parent)
def rowCount(self, parent: QModelIndex = QModelIndex()) -> int:
if parent.isValid():
return 0
return len(self.repos)
def columnCount(self, parent: QModelIndex = QModelIndex()) -> int:
if parent.isValid():
return 0
return 1
def data(self, index: QModelIndex, role: int = Qt.DisplayRole):
if not index.isValid():
return None
row = index.row()
if role == Qt.ToolTipRole:
tooltip = ""
if self.repos[row].repo_type == AddonManagerRepo.RepoType.PACKAGE:
tooltip = translate(
"AddonsInstaller", "Click for details about package {}"
).format(self.repos[row].display_name)
elif self.repos[row].repo_type == AddonManagerRepo.RepoType.WORKBENCH:
tooltip = translate(
"AddonsInstaller", "Click for details about workbench {}"
).format(self.repos[row].display_name)
elif self.repos[row].repo_type == AddonManagerRepo.RepoType.MACRO:
tooltip = translate(
"AddonsInstaller", "Click for details about macro {}"
).format(self.repos[row].display_name)
return tooltip
elif role == PackageListItemModel.DataAccessRole:
return self.repos[row]
def headerData(self, section, orientation, role=Qt.DisplayRole):
return None
def setData(self, index: QModelIndex, value, role=Qt.EditRole) -> None:
"""Set the data for this row. The column of the index is ignored."""
row = index.row()
self.write_lock.acquire()
if role == PackageListItemModel.StatusUpdateRole:
self.repos[row].update_status = value
self.dataChanged.emit(
self.index(row, 2),
self.index(row, 2),
[PackageListItemModel.StatusUpdateRole],
)
elif role == PackageListItemModel.IconUpdateRole:
self.repos[row].icon = value
self.dataChanged.emit(
self.index(row, 0),
self.index(row, 0),
[PackageListItemModel.IconUpdateRole],
)
self.write_lock.release()
def append_item(self, repo: AddonManagerRepo) -> None:
if repo in self.repos:
# Cowardly refuse to insert the same repo a second time
return
self.write_lock.acquire()
self.beginInsertRows(QModelIndex(), self.rowCount(), self.rowCount())
self.repos.append(repo)
self.endInsertRows()
self.write_lock.release()
def clear(self) -> None:
if self.rowCount() > 0:
self.write_lock.acquire()
self.beginRemoveRows(QModelIndex(), 0, self.rowCount() - 1)
self.repos = []
self.endRemoveRows()
self.write_lock.release()
def update_item_status(
self, name: str, status: AddonManagerRepo.UpdateStatus
) -> None:
for row, item in enumerate(self.repos):
if item.name == name:
self.setData(
self.index(row, 0), status, PackageListItemModel.StatusUpdateRole
)
return
def update_item_icon(self, name: str, icon: QIcon) -> None:
for row, item in enumerate(self.repos):
if item.name == name:
self.setData(
self.index(row, 0), icon, PackageListItemModel.IconUpdateRole
)
return
def reload_item(self, repo: AddonManagerRepo) -> None:
for index, item in enumerate(self.repos):
if item.name == repo.name:
self.write_lock.acquire()
self.repos[index] = repo
self.write_lock.release()
return
class CompactView(QWidget):
"""A single-line view of the package information"""
from compact_view import Ui_CompactView
def __init__(self, parent=None):
super().__init__(parent)
self.ui = Ui_CompactView()
self.ui.setupUi(self)
class ExpandedView(QWidget):
"""A multi-line view of the package information"""
from expanded_view import Ui_ExpandedView
def __init__(self, parent=None):
super().__init__(parent)
self.ui = Ui_ExpandedView()
self.ui.setupUi(self)
class PackageListItemDelegate(QStyledItemDelegate):
"""Render the repo data as a formatted region"""
def __init__(self, parent=None):
super().__init__(parent)
self.displayStyle = ListDisplayStyle.EXPANDED
self.expanded = ExpandedView()
self.compact = CompactView()
self.widget = self.expanded
def set_view(self, style: ListDisplayStyle) -> None:
if not self.displayStyle == style:
self.displayStyle = style
def sizeHint(self, option, index):
self.update_content(index)
return self.widget.sizeHint()
def update_content(self, index):
repo = index.data(PackageListItemModel.DataAccessRole)
if self.displayStyle == ListDisplayStyle.EXPANDED:
self.widget = self.expanded
self.widget.ui.labelPackageName.setText(f"<h1>{repo.display_name}</h1>")
self.widget.ui.labelIcon.setPixmap(repo.icon.pixmap(QSize(48, 48)))
else:
self.widget = self.compact
self.widget.ui.labelPackageName.setText(f"<b>{repo.display_name}</b>")
self.widget.ui.labelIcon.setPixmap(repo.icon.pixmap(QSize(16, 16)))
self.widget.ui.labelIcon.setText("")
if repo.metadata:
self.widget.ui.labelDescription.setText(repo.metadata.Description)
self.widget.ui.labelVersion.setText(f"<i>v{repo.metadata.Version}</i>")
if self.displayStyle == ListDisplayStyle.EXPANDED:
maintainers = repo.metadata.Maintainer
string = ""
if len(maintainers) == 1:
string = (
translate("AddonsInstaller", "Maintainer")
+ f": {maintainers[0]['name']} <{maintainers[0]['email']}>"
)
elif len(maintainers) > 1:
n = len(maintainers)
string = translate("AddonsInstaller", "Maintainers:", "", n)
for maintainer in maintainers:
string += f"\n{maintainer['name']} <{maintainer['email']}>"
self.widget.ui.labelMaintainer.setText(string)
elif repo.macro and repo.macro.parsed:
self.widget.ui.labelDescription.setText(repo.macro.comment)
self.widget.ui.labelVersion.setText(repo.macro.version)
if repo.macro.date:
if repo.macro.version:
new_label = (
"v"
+ repo.macro.version
+ ", "
+ translate("AddonsInstaller", "updated")
+ " "
+ repo.macro.date
)
else:
new_label = (
translate("AddonsInstaller", "Updated") + " " + repo.macro.date
)
self.widget.ui.labelVersion.setText(new_label)
if self.displayStyle == ListDisplayStyle.EXPANDED:
if repo.macro.author:
caption = translate("AddonsInstaller", "Author")
self.widget.ui.labelMaintainer.setText(
caption + ": " + repo.macro.author
)
else:
self.widget.ui.labelMaintainer.setText("")
else:
self.widget.ui.labelDescription.setText("")
self.widget.ui.labelVersion.setText("")
if self.displayStyle == ListDisplayStyle.EXPANDED:
self.widget.ui.labelMaintainer.setText("")
# Update status
if self.displayStyle == ListDisplayStyle.EXPANDED:
self.widget.ui.labelStatus.setText(self.get_expanded_update_string(repo))
else:
self.widget.ui.labelStatus.setText(self.get_compact_update_string(repo))
self.widget.adjustSize()
def get_compact_update_string(self, repo: AddonManagerRepo) -> str:
"""Get a single-line string listing details about the installed version and date"""
result = ""
if repo.update_status == AddonManagerRepo.UpdateStatus.UNCHECKED:
result = translate("AddonsInstaller", "Installed")
elif repo.update_status == AddonManagerRepo.UpdateStatus.NO_UPDATE_AVAILABLE:
result = translate("AddonsInstaller", "Up-to-date")
elif repo.update_status == AddonManagerRepo.UpdateStatus.UPDATE_AVAILABLE:
result = translate("AddonsInstaller", "Update available")
elif repo.update_status == AddonManagerRepo.UpdateStatus.PENDING_RESTART:
result = translate("AddonsInstaller", "Pending restart")
return result
def get_expanded_update_string(self, repo: AddonManagerRepo) -> str:
"""Get a multi-line string listing details about the installed version and date"""
result = ""
installed_version_string = ""
if repo.update_status != AddonManagerRepo.UpdateStatus.NOT_INSTALLED:
if repo.installed_version:
installed_version_string = (
"\n" + translate("AddonsInstaller", "Installed version") + ": "
)
installed_version_string += repo.installed_version
else:
installed_version_string = "\n" + translate(
"AddonsInstaller", "Unknown version"
)
installed_date_string = ""
if repo.updated_timestamp:
installed_date_string = (
"\n" + translate("AddonsInstaller", "Installed on") + ": "
)
installed_date_string += (
QDateTime.fromTime_t(repo.updated_timestamp)
.date()
.toString(Qt.SystemLocaleShortDate)
)
available_version_string = ""
if repo.metadata:
available_version_string = (
"\n" + translate("AddonsInstaller", "Available version") + ": "
)
available_version_string += repo.metadata.Version
if repo.update_status == AddonManagerRepo.UpdateStatus.UNCHECKED:
result = translate("AddonsInstaller", "Installed")
result += installed_version_string
result += installed_date_string
elif repo.update_status == AddonManagerRepo.UpdateStatus.NO_UPDATE_AVAILABLE:
result = translate("AddonsInstaller", "Up-to-date")
result += installed_version_string
result += installed_date_string
elif repo.update_status == AddonManagerRepo.UpdateStatus.UPDATE_AVAILABLE:
result = translate("AddonsInstaller", "Update available")
result += installed_version_string
result += installed_date_string
result += available_version_string
elif repo.update_status == AddonManagerRepo.UpdateStatus.PENDING_RESTART:
result = translate("AddonsInstaller", "Pending restart")
return result
def paint(self, painter: QPainter, option: QStyleOptionViewItem, _: QModelIndex):
painter.save()
self.widget.resize(option.rect.size())
painter.translate(option.rect.topLeft())
self.widget.render(painter, QPoint(), QRegion(), QWidget.DrawChildren)
painter.restore()
class PackageListFilter(QSortFilterProxyModel):
"""Handle filtering the item list on various criteria"""
def __init__(self):
super().__init__()
self.package_type = 0 # Default to showing everything
self.status = 0 # Default to showing any
self.setSortCaseSensitivity(Qt.CaseInsensitive)
self.hide_obsolete = False
self.hide_py2 = False
def setPackageFilter(
self, type: int
) -> None: # 0=All, 1=Workbenches, 2=Macros, 3=Preference Packs
self.package_type = type
self.invalidateFilter()
def setStatusFilter(
self, status: int
) -> None: # 0=Any, 1=Installed, 2=Not installed, 3=Update available
self.status = status
self.invalidateFilter()
def setHidePy2(self, hide_py2: bool) -> None:
self.hide_py2 = hide_py2
self.invalidateFilter()
def setHideObsolete(self, hide_obsolete: bool) -> None:
self.hide_obsolete = hide_obsolete
self.invalidateFilter()
def lessThan(self, left, right) -> bool:
l = self.sourceModel().data(left, PackageListItemModel.DataAccessRole)
r = self.sourceModel().data(right, PackageListItemModel.DataAccessRole)
return l.display_name.lower() < r.display_name.lower()
def filterAcceptsRow(self, row, parent=QModelIndex()):
index = self.sourceModel().createIndex(row, 0)
data = self.sourceModel().data(index, PackageListItemModel.DataAccessRole)
if self.package_type == 1:
if not data.contains_workbench():
return False
elif self.package_type == 2:
if not data.contains_macro():
return False
elif self.package_type == 3:
if not data.contains_preference_pack():
return False
if self.status == StatusFilter.INSTALLED:
if data.update_status == AddonManagerRepo.UpdateStatus.NOT_INSTALLED:
return False
elif self.status == StatusFilter.NOT_INSTALLED:
if data.update_status != AddonManagerRepo.UpdateStatus.NOT_INSTALLED:
return False
elif self.status == StatusFilter.UPDATE_AVAILABLE:
if data.update_status != AddonManagerRepo.UpdateStatus.UPDATE_AVAILABLE:
return False
# If it's not installed, check to see if it's Py2 only
if (
data.update_status == AddonManagerRepo.UpdateStatus.NOT_INSTALLED
and self.hide_py2
and data.python2
):
return False
# If it's not installed, check to see if it's marked obsolete
if (
data.update_status == AddonManagerRepo.UpdateStatus.NOT_INSTALLED
and self.hide_obsolete
and data.obsolete
):
return False
name = data.display_name
desc = data.description
if hasattr(self, "filterRegularExpression"): # Added in Qt 5.12
re = self.filterRegularExpression()
if re.isValid():
re.setPatternOptions(QRegularExpression.CaseInsensitiveOption)
if re.match(name).hasMatch():
return True
if re.match(desc).hasMatch():
return True
if (
data.macro
and data.macro.comment
and re.match(data.macro.comment).hasMatch()
):
return True
return False
else:
return False
else:
re = self.filterRegExp()
if re.isValid():
re.setCaseSensitivity(Qt.CaseInsensitive)
if re.indexIn(name) != -1:
return True
if re.indexIn(desc) != -1:
return True
return False
else:
return False
class Ui_PackageList(object):
"""The contents of the PackageList widget"""
def setupUi(self, Form):
if not Form.objectName():
Form.setObjectName("PackageList")
self.verticalLayout = QVBoxLayout(Form)
self.verticalLayout.setObjectName("verticalLayout")
self.horizontalLayout_6 = QHBoxLayout()
self.horizontalLayout_6.setObjectName("horizontalLayout_6")
self.buttonCompactLayout = QToolButton(Form)
self.buttonCompactLayout.setObjectName("buttonCompactLayout")
self.buttonCompactLayout.setCheckable(True)
self.buttonCompactLayout.setAutoExclusive(True)
self.buttonCompactLayout.setIcon(
QIcon.fromTheme("expanded_view", QIcon(":/icons/compact_view.svg"))
)
self.horizontalLayout_6.addWidget(self.buttonCompactLayout)
self.buttonExpandedLayout = QToolButton(Form)
self.buttonExpandedLayout.setObjectName("buttonExpandedLayout")
self.buttonExpandedLayout.setCheckable(True)
self.buttonExpandedLayout.setChecked(True)
self.buttonExpandedLayout.setAutoExclusive(True)
self.buttonExpandedLayout.setIcon(
QIcon.fromTheme("expanded_view", QIcon(":/icons/expanded_view.svg"))
)
self.horizontalLayout_6.addWidget(self.buttonExpandedLayout)
self.labelPackagesContaining = QLabel(Form)
self.labelPackagesContaining.setObjectName("labelPackagesContaining")
self.horizontalLayout_6.addWidget(self.labelPackagesContaining)
self.comboPackageType = QComboBox(Form)
self.comboPackageType.addItem("")
self.comboPackageType.addItem("")
self.comboPackageType.addItem("")
self.comboPackageType.addItem("")
self.comboPackageType.setObjectName("comboPackageType")
self.horizontalLayout_6.addWidget(self.comboPackageType)
self.labelStatus = QLabel(Form)
self.labelStatus.setObjectName("labelStatus")
self.horizontalLayout_6.addWidget(self.labelStatus)
self.comboStatus = QComboBox(Form)
self.comboStatus.addItem("")
self.comboStatus.addItem("")
self.comboStatus.addItem("")
self.comboStatus.addItem("")
self.comboStatus.setObjectName("comboStatus")
self.horizontalLayout_6.addWidget(self.comboStatus)
self.lineEditFilter = QLineEdit(Form)
self.lineEditFilter.setObjectName("lineEditFilter")
self.lineEditFilter.setClearButtonEnabled(True)
self.horizontalLayout_6.addWidget(self.lineEditFilter)
self.labelFilterValidity = QLabel(Form)
self.labelFilterValidity.setObjectName("labelFilterValidity")
self.horizontalLayout_6.addWidget(self.labelFilterValidity)
self.verticalLayout.addLayout(self.horizontalLayout_6)
self.listPackages = QListView(Form)
self.listPackages.setObjectName("listPackages")
self.listPackages.setEditTriggers(QAbstractItemView.NoEditTriggers)
self.listPackages.setProperty("showDropIndicator", False)
self.listPackages.setSelectionMode(QAbstractItemView.NoSelection)
self.listPackages.setResizeMode(QListView.Adjust)
self.listPackages.setUniformItemSizes(False)
self.listPackages.setAlternatingRowColors(True)
self.listPackages.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
self.verticalLayout.addWidget(self.listPackages)
self.retranslateUi(Form)
QMetaObject.connectSlotsByName(Form)
def retranslateUi(self, Form):
self.labelPackagesContaining.setText(
QCoreApplication.translate(
"AddonsInstaller", "Show Addons containing:", None
)
)
self.comboPackageType.setItemText(
0, QCoreApplication.translate("AddonsInstaller", "All", None)
)
self.comboPackageType.setItemText(
1, QCoreApplication.translate("AddonsInstaller", "Workbenches", None)
)
self.comboPackageType.setItemText(
2, QCoreApplication.translate("AddonsInstaller", "Macros", None)
)
self.comboPackageType.setItemText(
3, QCoreApplication.translate("AddonsInstaller", "Preference Packs", None)
)
self.labelStatus.setText(
QCoreApplication.translate("AddonsInstaller", "Status:", None)
)
self.comboStatus.setItemText(
StatusFilter.ANY, QCoreApplication.translate("AddonsInstaller", "Any", None)
)
self.comboStatus.setItemText(
StatusFilter.INSTALLED,
QCoreApplication.translate("AddonsInstaller", "Installed", None),
)
self.comboStatus.setItemText(
StatusFilter.NOT_INSTALLED,
QCoreApplication.translate("AddonsInstaller", "Not installed", None),
)
self.comboStatus.setItemText(
StatusFilter.UPDATE_AVAILABLE,
QCoreApplication.translate("AddonsInstaller", "Update available", None),
)
self.lineEditFilter.setPlaceholderText(
QCoreApplication.translate("AddonsInstaller", "Filter", None)
)
self.labelFilterValidity.setText(
QCoreApplication.translate("AddonsInstaller", "OK", None)
)