Skip to content

Commit 0116139

Browse files
authored
Merge 064d971 into 3ce266b
2 parents 3ce266b + 064d971 commit 0116139

File tree

8 files changed

+201
-38
lines changed

8 files changed

+201
-38
lines changed

source/core.py

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
import time
1919
import ctypes
2020
import logHandler
21+
import languageHandler
2122
import globalVars
2223
from logHandler import log
2324
import addonHandler
@@ -146,7 +147,9 @@ def restart(disableAddons=False, debugLogging=False):
146147
log.error("NVDA already in process of exiting, this indicates a logic error.")
147148
return
148149
import subprocess
149-
for paramToRemove in ("--disable-addons", "--debug-logging", "--ease-of-access"):
150+
for paramToRemove in (
151+
"--disable-addons", "--debug-logging", "--ease-of-access"
152+
) + languageHandler.getLanguageCliArgs():
150153
try:
151154
sys.argv.remove(paramToRemove)
152155
except ValueError:
@@ -175,7 +178,6 @@ def resetConfiguration(factoryDefaults=False):
175178
import brailleInput
176179
import speech
177180
import vision
178-
import languageHandler
179181
import inputCore
180182
import tones
181183
log.debug("Terminating vision")
@@ -193,8 +195,11 @@ def resetConfiguration(factoryDefaults=False):
193195
log.debug("Reloading config")
194196
config.conf.reset(factoryDefaults=factoryDefaults)
195197
logHandler.setLogLevelFromConfig()
196-
#Language
197-
lang = config.conf["general"]["language"]
198+
# Language
199+
if languageHandler.isLanguageForced():
200+
lang = globalVars.appArgs.language
201+
else:
202+
lang = config.conf["general"]["language"]
198203
log.debug("setting language to %s"%lang)
199204
languageHandler.setLanguage(lang)
200205
# Addons
@@ -237,7 +242,6 @@ def _setInitialFocus():
237242

238243

239244
def getWxLangOrNone() -> Optional['wx.LanguageInfo']:
240-
import languageHandler
241245
import wx
242246
lang = languageHandler.getLanguage()
243247
wxLocaleObj = wx.Locale()
@@ -408,8 +412,10 @@ def main():
408412
except:
409413
pass
410414
logHandler.setLogLevelFromConfig()
411-
lang = config.conf["general"]["language"]
412-
import languageHandler
415+
if languageHandler.isLanguageForced():
416+
lang = globalVars.appArgs.language
417+
else:
418+
lang = config.conf["general"]["language"]
413419
log.debug(f"setting language to {lang}")
414420
languageHandler.setLanguage(lang)
415421
log.info(f"Windows version: {winVersion.getWinVer()}")
@@ -597,7 +603,7 @@ def handlePowerStatusChange(self):
597603
log.error("Failed to initialize wx locale",exc_info=True)
598604
finally:
599605
# Revert wx's changes to the python locale
600-
languageHandler.setLocale(languageHandler.curLang)
606+
languageHandler.setLocale(languageHandler.getLanguage())
601607

602608
log.debug("Initializing garbageHandler")
603609
garbageHandler.initialize()

source/gui/__init__.py

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
import speechViewer
3838
import winUser
3939
import api
40+
import languageHandler
4041

4142
try:
4243
import updateCheck
@@ -642,12 +643,23 @@ def __init__(self, parent):
642643
dialog = self
643644
mainSizer = wx.BoxSizer(wx.VERTICAL)
644645

646+
warningMessages = []
645647
contentSizerHelper = guiHelper.BoxSizerHelper(self, orientation=wx.VERTICAL)
646648

647649
if globalVars.appArgs.disableAddons:
648650
# Translators: A message in the exit Dialog shown when all add-ons are disabled.
649651
addonsDisabledText = _("All add-ons are now disabled. They will be re-enabled on the next restart unless you choose to disable them again.")
650-
contentSizerHelper.addItem(wx.StaticText(self, wx.ID_ANY, label=addonsDisabledText))
652+
warningMessages.append(addonsDisabledText)
653+
if languageHandler.isLanguageForced():
654+
# Translators: A message in the exit Dialog shown when NVDA language has been
655+
# overwritten from the command line.
656+
langForcedMsg = _(
657+
"\nNVDA's interface language is now forced from the command line."
658+
" On the next restart, the language saved in NVDA's configuration will be used instead."
659+
)
660+
warningMessages.append(langForcedMsg)
661+
if warningMessages:
662+
contentSizerHelper.addItem(wx.StaticText(self, wx.ID_ANY, label="\n".join(warningMessages)))
651663

652664
# Translators: The label for actions list in the Exit dialog.
653665
labelText=_("What would you like to &do?")

source/gui/settingsDialogs.py

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -713,19 +713,29 @@ def makeSettings(self, settingsSizer):
713713
settingsSizerHelper = guiHelper.BoxSizerHelper(self, sizer=settingsSizer)
714714
self.languageNames = languageHandler.getAvailableLanguages(presentational=True)
715715
languageChoices = [x[1] for x in self.languageNames]
716+
if languageHandler.isLanguageForced():
717+
cmdLangDescription = [
718+
ld[1] for ld in self.languageNames if ld[0] == globalVars.appArgs.language
719+
][0]
720+
languageChoices.append(
721+
# Translators: Shown for a language which has been provided from the command line
722+
# 'langDesc' would be replaced with description of the given locale.
723+
_("Command line option: {langDesc}").format(langDesc=cmdLangDescription)
724+
)
725+
self.languageNames.append("FORCED")
716726
# Translators: The label for a setting in general settings to select NVDA's interface language
717727
# (once selected, NVDA must be restarted; the option user default means the user's Windows language
718728
# will be used).
719729
languageLabelText = _("NVDA &Language (requires restart):")
720730
self.languageList=settingsSizerHelper.addLabeledControl(languageLabelText, wx.Choice, choices=languageChoices)
721731
self.bindHelpEvent("GeneralSettingsLanguage", self.languageList)
722732
self.languageList.SetToolTip(wx.ToolTip("Choose the language NVDA's messages and user interface should be presented in."))
723-
try:
724-
self.oldLanguage=config.conf["general"]["language"]
725-
index=[x[0] for x in self.languageNames].index(self.oldLanguage)
726-
self.languageList.SetSelection(index)
727-
except:
728-
pass
733+
self.oldLanguage = config.conf["general"]["language"]
734+
if languageHandler.isLanguageForced():
735+
index = len(self.languageNames) - 1
736+
else:
737+
index = [x[0] for x in self.languageNames].index(self.oldLanguage)
738+
self.languageList.SetSelection(index)
729739
if globalVars.appArgs.secure:
730740
self.languageList.Disable()
731741

@@ -884,8 +894,12 @@ def onCopySettings(self,evt):
884894
gui.messageBox(_("Successfully copied NVDA user settings"),_("Success"),wx.OK|wx.ICON_INFORMATION,self)
885895

886896
def onSave(self):
887-
newLanguage=[x[0] for x in self.languageNames][self.languageList.GetSelection()]
888-
config.conf["general"]["language"]=newLanguage
897+
if(
898+
not languageHandler.isLanguageForced()
899+
or self.languageList.GetSelection() != len(self.languageNames) - 1
900+
):
901+
newLanguage = [x[0] for x in self.languageNames][self.languageList.GetSelection()]
902+
config.conf["general"]["language"] = newLanguage
889903
config.conf["general"]["saveConfigurationOnExit"]=self.saveOnExitCheckBox.IsChecked()
890904
config.conf["general"]["askToExit"]=self.askToExitCheckBox.IsChecked()
891905
config.conf["general"]["playStartAndExitSounds"]=self.playStartAndExitSoundsCheckBox.IsChecked()

source/languageHandler.py

Lines changed: 51 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# A part of NonVisual Desktop Access (NVDA)
2-
# Copyright (C) 2007-2021 NV access Limited, Joseph Lee
2+
# Copyright (C) 2007-2021 NV access Limited, Joseph Lee, Łukasz Golonka
33
# This file is covered by the GNU General Public License.
44
# See the file COPYING for more details.
55

@@ -18,7 +18,7 @@
1818
import globalVars
1919
from logHandler import log
2020
import winKernel
21-
from typing import Optional
21+
from typing import List, Optional, Tuple
2222

2323
#a few Windows locale constants
2424
LOCALE_USER_DEFAULT = 0x400
@@ -118,8 +118,12 @@ def windowsLCIDToLocaleName(lcid: int) -> Optional[str]:
118118
if lang:
119119
return normalizeLanguage(lang)
120120

121-
def getLanguageDescription(language):
121+
122+
def getLanguageDescription(language: str) -> Optional[str]:
122123
"""Finds out the description (localized full name) of a given local name"""
124+
if language == "Windows":
125+
# Translators: the label for the Windows default NVDA interface language.
126+
return _("User default")
123127
desc=None
124128
LCID=localeNameToWindowsLCID(language)
125129
if LCID is not LCID_NONE:
@@ -218,21 +222,27 @@ def ansiCodePageFromNVDALocale(localeName: str) -> Optional[str]:
218222
return None
219223

220224

221-
def getAvailableLanguages(presentational=False):
222-
"""generates a list of locale names, plus their full localized language and country names.
223-
@param presentational: whether this is meant to be shown alphabetically by language description
224-
@type presentational: bool
225-
@rtype: list of tuples
226-
"""
227-
#Make a list of all the locales found in NVDA's locale dir
225+
def listNVDALocales() -> List[str]:
226+
# Make a list of all the locales found in NVDA's locale dir
228227
localesDir = os.path.join(globalVars.appDir, 'locale')
229228
locales = [
230229
x for x in os.listdir(localesDir) if os.path.isfile(os.path.join(localesDir, x, 'LC_MESSAGES', 'nvda.mo'))
231230
]
232-
#Make sure that en (english) is in the list as it may not have any locale files, but is default
231+
# Make sure that en (english) is in the list as it may not have any locale files, but is default
233232
if 'en' not in locales:
234233
locales.append('en')
235234
locales.sort()
235+
# include a 'user default, windows' language,
236+
# which just represents the default language for this user account
237+
locales.insert(0, "Windows")
238+
return locales
239+
240+
241+
def getAvailableLanguages(presentational: bool = False) -> List[Tuple[str, str]]:
242+
"""generates a list of locale names, plus their full localized language and country names.
243+
@param presentational: whether this is meant to be shown alphabetically by language description
244+
"""
245+
locales = listNVDALocales()
236246
# Prepare a 2-tuple list of language code and human readable language description.
237247
langs = [(lc, getLanguageDescription(lc)) for lc in locales]
238248
# Translators: The pattern defining how languages are displayed and sorted in in the general
@@ -243,13 +253,14 @@ def getAvailableLanguages(presentational=False):
243253
isDescFirst = fullDescPattern.find("{desc}") < fullDescPattern.find("{lc}")
244254
if presentational and isDescFirst:
245255
langs.sort(key=lambda lang: locale.strxfrm(lang[1] if lang[1] else lang[0]))
246-
langs = [(lc, (fullDescPattern.format(desc=desc, lc=lc) if desc else lc)) for lc, desc in langs]
247-
#include a 'user default, windows' language, which just represents the default language for this user account
248-
langs.insert(
249-
0,
250-
# Translators: the label for the Windows default NVDA interface language.
251-
("Windows", _("User default"))
252-
)
256+
# Make sure that the 'user default' language is first in the list.
257+
for index, lang in enumerate(langs):
258+
if lang[0] == "Windows":
259+
break
260+
userDefault = langs.pop(index)
261+
langs = [userDefault] + [
262+
(lc, (fullDescPattern.format(desc=desc, lc=lc) if desc else lc)) for lc, desc in langs
263+
]
253264
return langs
254265

255266

@@ -274,6 +285,27 @@ def pgettext(context, message):
274285
raise ValueError("%s is Not a GNUTranslations or NullTranslations object" % translations)
275286
return pgettext
276287

288+
289+
def getLanguageCliArgs() -> Tuple[str, ...]:
290+
"""Returns all command line arguments which were used to set current NVDA language
291+
or an empty tuple if language has not been specified from the CLI."""
292+
for argIndex, argValue in enumerate(sys.argv):
293+
if argValue == "--lang":
294+
# Language was provided in a form `--lang lang_CODE`. The next position in `sys.argv` is a language code.
295+
# It is impossible not to provide it in this case as it would be flagged as an error
296+
# during arguments validation.
297+
return (argValue, sys.argv[argIndex + 1])
298+
if argValue.startswith("--lang="):
299+
# Language in a form `--lang=lang_CODE`
300+
return (argValue,)
301+
return tuple()
302+
303+
304+
def isLanguageForced() -> bool:
305+
"""Returns `True` if language is provided from the command line - `False` otherwise."""
306+
return bool(getLanguageCliArgs())
307+
308+
277309
def getWindowsLanguage():
278310
"""
279311
Fetches the locale name of the user's configured language in Windows.
@@ -423,7 +455,7 @@ def getLanguage() -> str:
423455
return curLang
424456

425457

426-
def normalizeLanguage(lang) -> Optional[str]:
458+
def normalizeLanguage(lang: str) -> Optional[str]:
427459
"""
428460
Normalizes a language-dialect string in to a standard form we can deal with.
429461
Converts any dash to underline, and makes sure that language is lowercase and dialect is upercase.

source/nvda.pyw

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,7 @@ if not winVersion.isSupportedOS():
112112
winUser.MessageBox(0, ctypes.FormatError(winUser.ERROR_OLD_WIN_VERSION), None, winUser.MB_ICONERROR)
113113
sys.exit(1)
114114

115+
115116
def stringToBool(string):
116117
"""Wrapper for configobj.validate.is_boolean to raise the proper exception for wrong values."""
117118
from configobj.validate import is_boolean, ValidateError
@@ -121,6 +122,22 @@ def stringToBool(string):
121122
raise argparse.ArgumentTypeError(e.message)
122123

123124

125+
def stringToLang(value: str) -> str:
126+
"""Perform basic case normalization for ease of use.
127+
"""
128+
import languageHandler
129+
if value.casefold() == "Windows".casefold():
130+
normalizedLang = "Windows"
131+
else:
132+
normalizedLang = languageHandler.normalizeLanguage(value)
133+
possibleLangNames = languageHandler.listNVDALocales()
134+
if normalizedLang is not None and normalizedLang in possibleLangNames:
135+
return normalizedLang
136+
raise argparse.ArgumentTypeError(
137+
f"Language code should be one of:\n{', '.join(possibleLangNames)}."
138+
)
139+
140+
124141
#Process option arguments
125142
parser=NoConsoleOptionParser()
126143
quitGroup = parser.add_mutually_exclusive_group()
@@ -129,6 +146,16 @@ parser.add_argument('-k','--check-running',action="store_true",dest='check_runni
129146
parser.add_argument('-f','--log-file',dest='logFileName',type=str,help="The file where log messages should be written to")
130147
parser.add_argument('-l','--log-level',dest='logLevel',type=int,default=0,choices=[10, 12, 15, 20, 30, 40, 50, 100],help="The lowest level of message logged (debug 10, input/output 12, debugwarning 15, info 20, warning 30, error 40, critical 50, off 100), default is info")
131148
parser.add_argument('-c','--config-path',dest='configPath',default=None,type=str,help="The path where all settings for NVDA are stored")
149+
parser.add_argument(
150+
'--lang',
151+
dest='language',
152+
default=None,
153+
type=stringToLang,
154+
help=(
155+
"Override the configured NVDA language."
156+
" Set to \"Windows\" for current user default, \"en\" for English, etc."
157+
)
158+
)
132159
parser.add_argument('-m','--minimal',action="store_true",dest='minimal',default=False,help="No sounds, no interface, no start message etc")
133160
parser.add_argument('-s','--secure',action="store_true",dest='secure',default=False,help="Secure mode (disable Python console)")
134161
parser.add_argument('--disable-addons',action="store_true",dest='disableAddons',default=False,help="Disable all add-ons")

0 commit comments

Comments
 (0)