Repository: nvaccess/nvda

Issue:
Python 3.8: use gettext.pgettext function for translatable strings with contexts
Hi,

Blocked by #7105 and Python 3.8:

### Is your feature request related to a problem? Please describe.
In 2012, as part of adding message contexts for various translatable messages (see #1524 for details), NVDA added pgettext function to built-in namespace. Fast forward to 2019, and one of the additions in python 3.8 (later in 2019) will be gettext.pgettext family of functions (see python/cpython:7253) which will allow NVDA to add context messages for translatable strings without modifying built-in namespace.

### Describe the solution you'd like
once #7105 is complete and when it comes time to upgrade to Python 3.8 or later, consider using gettext.pgettext.

### Describe alternatives you've considered
None

### Additional context
See #7105 for details on Python 3 transition workflow, as well as CPython pull request noted above for further details.

Thanks.


Discussion:
Hi,

Blocked until at least wxPython is built with Python 3.8 support.

Note that there is a proposal that seeks to speed up Python releases. If this happens, Python 3.9 will hit the air in June 2020, meaning we might skip Python 3.8 and go directly to 3.9. Again this introduces another block until C extensions (wxPython, for instance) are ready.

Thanks.
Hi,

2020: blocked until NVDA is built with Python 3.8, and only then this issue will be revisited.

Thanks.
Hi,

2021 update: foundations being laid. The overall strategy wil be replacing pgettext call with gettext.pgettext and testing it.

Thanks.

Resolving patch:
diff --git a/source/addonHandler/__init__.py b/source/addonHandler/__init__.py
--- a/source/addonHandler/__init__.py
+++ b/source/addonHandler/__init__.py
@@ -1,9 +1,9 @@
 # -*- coding: UTF-8 -*-
-#addonHandler.py
-#A part of NonVisual Desktop Access (NVDA)
-#Copyright (C) 2012-2019 Rui Batista, NV Access Limited, Noelia Ruiz Martínez, Joseph Lee, Babbage B.V., Arnold Loubriat
-#This file is covered by the GNU General Public License.
-#See the file COPYING for more details.
+# A part of NonVisual Desktop Access (NVDA)
+# Copyright (C) 2012-2021 NV Access Limited, Rui Batista, Noelia Ruiz Martínez,
+# Joseph Lee, Babbage B.V., Arnold Loubriat
+# This file is covered by the GNU General Public License.
+# See the file COPYING for more details.
 
 import sys
 import os.path
@@ -539,8 +539,8 @@ def initTranslation():
 	try:
 		callerFrame = inspect.currentframe().f_back
 		callerFrame.f_globals['_'] = translations.gettext
-		# Install our pgettext function.
-		callerFrame.f_globals['pgettext'] = languageHandler.makePgettext(translations)
+		# Install pgettext function.
+		callerFrame.f_globals['pgettext'] = translations.pgettext
 	finally:
 		del callerFrame # Avoid reference problems with frames (per python docs)
 
diff --git a/source/languageHandler.py b/source/languageHandler.py
--- a/source/languageHandler.py
+++ b/source/languageHandler.py
@@ -1,14 +1,12 @@
-#languageHandler.py
-#A part of NonVisual Desktop Access (NVDA)
-#Copyright (C) 2007-2018 NV access Limited, Joseph Lee
-#This file is covered by the GNU General Public License.
-#See the file COPYING for more details.
+# A part of NonVisual Desktop Access (NVDA)
+# Copyright (C) 2007-2021 NV Access Limited, Joseph Lee
+# This file is covered by the GNU General Public License.
+# See the file COPYING for more details.
 
 """Language and localization support.
 This module assists in NVDA going global through language services such as converting Windows locale ID's to friendly names and presenting available languages.
 """
 
-import builtins
 import os
 import sys
 import ctypes
@@ -123,27 +121,6 @@ def getAvailableLanguages(presentational=False):
 	)
 	return langs
 
-def makePgettext(translations):
-	"""Obtaina  pgettext function for use with a gettext translations instance.
-	pgettext is used to support message contexts,
-	but Python's gettext module doesn't support this,
-	so NVDA must provide its own implementation.
-	"""
-	if isinstance(translations, gettext.GNUTranslations):
-		def pgettext(context, message):
-			try:
-				# Look up the message with its context.
-				return translations._catalog[u"%s\x04%s" % (context, message)]
-			except KeyError:
-				return message
-	elif isinstance(translations, gettext.NullTranslations):
-		# A language with out a translation catalog, such as English.
-		def pgettext(context, message):
-			return message
-	else:
-		raise ValueError("%s is Not a GNUTranslations or NullTranslations object"%translations)
-	return pgettext
-
 def getWindowsLanguage():
 	"""
 	Fetches the locale name of the user's configured language in Windows.
@@ -198,10 +175,8 @@ def setLanguage(lang):
 	except IOError:
 		trans=gettext.translation("nvda",fallback=True)
 		curLang="en"
-	trans.install()
-	# Install our pgettext function.
-	import builtins
-	builtins.pgettext = makePgettext(trans)
+	# #9207: Python 3.8 adds gettext.pgettext, so add it to the built-in namespace.
+	trans.install(names=["pgettext"])
 
 def getLanguage():
 	return curLang


Test patch: