Skip to content

Instantly share code, notes, and snippets.

@ales-erjavec
Last active April 21, 2023 10:40
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ales-erjavec/9096e525c558cab9b77b235a0e4ba319 to your computer and use it in GitHub Desktop.
Save ales-erjavec/9096e525c558cab9b77b235a0e4ba319 to your computer and use it in GitHub Desktop.
"""
An experiment to fix PyQt QObject.tr context override
https://www.riverbankcomputing.com/static/Docs/PyQt5/i18n.html#differences-between-pyqt5-and-qt
.. note::
Known deficiencies:
* No support for @property, @pyQtSlot, etc. decorators
.. warning::
This is really an experiment not inteded to be used.
Example
-------
@fix_tr
class A(QObject):
def hello(self):
return self.tr("Hello")
class B(A):
pass
a = A()
a.hello()
b = B()
b.hello() # this translated in A context
"""
import sys
from types import FunctionType, CodeType
from PyQt5.QtCore import QObject, QCoreApplication, QTranslator
def CodeType_replace(
code: CodeType, **kwargs,
) -> CodeType:
"""
Replace the given members of `code`
This is the same as `CodeType.replace `on Python >= 3.8
"""
if sys.version_info >= (3, 8):
return code.replace(**kwargs)
return CodeType(
kwargs.get("co_argcount", code.co_argcount),
kwargs.get("co_kwonlyargcount", code.co_kwonlyargcount),
kwargs.get("co_nlocals", code.co_nlocals),
kwargs.get("co_stacksize", code.co_stacksize),
kwargs.get("co_flags", code.co_flags),
kwargs.get("co_code", code.co_code),
kwargs.get("co_consts", code.co_consts),
kwargs.get("co_names", code.co_names),
kwargs.get("co_varnames", code.co_varnames),
kwargs.get("co_filename", code.co_filename),
kwargs.get("co_name", code.co_name),
kwargs.get("co_firstlineno", code.co_firstlineno),
kwargs.get("co_lnotab", code.co_lnotab),
kwargs.get("co_freevars", code.co_freevars),
kwargs.get("co_cellvars", code.co_cellvars),
)
def rebind(
method: FunctionType,
fname: str,
fmangle_name: str
):
"""
Rebind a named attribute lookup within method.
.. note:: This reassigns the method's code object.
"""
code = method.__code__
names = code.co_names
if fname in names:
names_ = list(names)
idx = names_.index(fname)
names_[idx] = fmangle_name
code = CodeType_replace(
code,
co_names=tuple(names_)
)
method.__code__ = code
return method
def fix_tr(class_: type) -> type:
"""
Fix the `tr` method calls for methods defined in `class_`
Example
-------
>>> @fix_tr
... class A(QObject):
... def f(self):
... print(self.tr("Hello"))
...
>>> @fix_tr
... class B(A):
... def f(self):
... print(self.tr("Hello")) # This is in B context
... super().f() # This will translate in A context
"""
fname = "tr"
fmangled_name = f"_{class_.__name__}_{id(class_)}_tr"
methods = [
(name, rebind(f, fname, fmangled_name))
for name, f in class_.__dict__.items() if isinstance(f, FunctionType)]
namespace = dict(class_.__dict__)
namespace.update(methods)
def tr(self, *args, **kwargs):
return QCoreApplication.translate(class_.__name__, *args, **kwargs)
namespace[fmangled_name] = tr
for name, f in namespace.items():
setattr(class_, name, f)
return class_
@fix_tr
class A(QObject):
def f(self):
print(self.tr("Hello"))
@fix_tr
class B(A):
def f(self):
print(self.tr("Hello")) # This is in B context
super().f() # This will translate in A context
class Translator(QTranslator):
def translate(
self, context: str, sourceText: str, disambiguation="", n=-1
) -> str:
return context + " " + sourceText
t = Translator()
a = QCoreApplication([])
QCoreApplication.installTranslator(t)
A().f()
B().f()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment