mirror of
https://github.com/LorenEteval/Furious.git
synced 2026-10-08 14:57:57 +03:00
Translate unsupported sing-tun settings errors
Signed-off-by: Loren Eteval <loren.eteval@proton.me>
This commit is contained in:
Vendored
+32
@@ -3757,5 +3757,37 @@ TRANSLATION = {
|
||||
"RU": "Доступно, если для «Стек» выбрано go или «Автоматически».",
|
||||
"ZH": "协议栈设为go或自动时可用。",
|
||||
"isReviewed": "True"
|
||||
},
|
||||
"Unsupported sing-tun settings fields": {
|
||||
"source": [
|
||||
"Furious.Window.SingTUNSettingsDialog"
|
||||
],
|
||||
"RU": "Неподдерживаемые поля настроек sing-tun",
|
||||
"ZH": "不支持的sing-tun设置字段",
|
||||
"isReviewed": "True"
|
||||
},
|
||||
"Unsupported sing-tun stack": {
|
||||
"source": [
|
||||
"Furious.Window.SingTUNSettingsDialog"
|
||||
],
|
||||
"RU": "Неподдерживаемый сетевой стек sing-tun",
|
||||
"ZH": "不支持的sing-tun协议栈",
|
||||
"isReviewed": "True"
|
||||
},
|
||||
"Unsupported or application-owned native option": {
|
||||
"source": [
|
||||
"Furious.Window.SingTUNSettingsDialog"
|
||||
],
|
||||
"RU": "Неподдерживаемый или управляемый приложением нативный параметр",
|
||||
"ZH": "不支持的原生选项或由应用管理的选项",
|
||||
"isReviewed": "True"
|
||||
},
|
||||
"Unsupported sing-tun host options": {
|
||||
"source": [
|
||||
"Furious.Window.SingTUNSettingsDialog"
|
||||
],
|
||||
"RU": "Неподдерживаемые параметры хоста sing-tun",
|
||||
"ZH": "不支持的sing-tun主机选项",
|
||||
"isReviewed": "True"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,6 +19,8 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from enum import Enum
|
||||
|
||||
import copy
|
||||
import functools
|
||||
import ipaddress
|
||||
@@ -81,6 +83,21 @@ _STACK_FIELDS = {
|
||||
}
|
||||
|
||||
|
||||
class SingTUNUnsupportedSettingsError(ValueError):
|
||||
"""Identify unsupported settings independently of their diagnostic wording."""
|
||||
|
||||
class Reason(Enum):
|
||||
SettingsFields = 'Unsupported sing-tun settings fields'
|
||||
Stack = 'Unsupported sing-tun stack'
|
||||
NativeOption = 'Unsupported or application-owned native option'
|
||||
HostOptions = 'Unsupported sing-tun host options'
|
||||
|
||||
def __init__(self, reason: Reason):
|
||||
self.reason = reason
|
||||
|
||||
super().__init__(reason.value)
|
||||
|
||||
|
||||
class SingTUNHostSettingsCallers:
|
||||
"""Bind sing-tun's named host settings callers to a captured host document."""
|
||||
|
||||
@@ -156,7 +173,9 @@ def prepareSingTUNSettings(document, *, platform=None):
|
||||
raise ValueError('sing-tun settings must be an object')
|
||||
|
||||
if set(document) - set(SING_TUN_DEFAULTS):
|
||||
raise ValueError('Unsupported sing-tun settings fields')
|
||||
raise SingTUNUnsupportedSettingsError(
|
||||
SingTUNUnsupportedSettingsError.Reason.SettingsFields
|
||||
)
|
||||
|
||||
result = copy.deepcopy(SING_TUN_DEFAULTS)
|
||||
|
||||
@@ -170,7 +189,9 @@ def prepareSingTUNSettings(document, *, platform=None):
|
||||
result[key] = copy.deepcopy(value)
|
||||
|
||||
if result['stack'] not in ('', 'go', 'gvisor', 'system', 'mixed'):
|
||||
raise ValueError('Unsupported sing-tun stack')
|
||||
raise SingTUNUnsupportedSettingsError(
|
||||
SingTUNUnsupportedSettingsError.Reason.Stack
|
||||
)
|
||||
|
||||
if result['log_level'] not in ('trace', 'debug', 'info', 'warn', 'error', 'silent'):
|
||||
raise ValueError('Invalid sing-tun log level')
|
||||
@@ -201,7 +222,9 @@ def prepareSingTUNSettings(document, *, platform=None):
|
||||
tun, stack = result['tun_options'], result['stack_options']
|
||||
|
||||
if set(tun) - _TUN_FIELDS or set(stack) - _STACK_FIELDS:
|
||||
raise ValueError('Unsupported or application-owned native option')
|
||||
raise SingTUNUnsupportedSettingsError(
|
||||
SingTUNUnsupportedSettingsError.Reason.NativeOption
|
||||
)
|
||||
|
||||
for key, expected in _OWNED.items():
|
||||
if type(tun[key]) is not type(expected) or tun[key] != expected:
|
||||
@@ -303,7 +326,9 @@ def prepareSingHostSettings(document):
|
||||
raise ValueError('sing-tun host options must be an object')
|
||||
|
||||
if set(document) - set(SING_TUN_HOST_DEFAULTS):
|
||||
raise ValueError('Unsupported sing-tun host options')
|
||||
raise SingTUNUnsupportedSettingsError(
|
||||
SingTUNUnsupportedSettingsError.Reason.HostOptions
|
||||
)
|
||||
|
||||
result = copy.deepcopy(SING_TUN_HOST_DEFAULTS)
|
||||
result.update(copy.deepcopy(document))
|
||||
|
||||
@@ -22,6 +22,7 @@ from __future__ import annotations
|
||||
from Furious.Models.SingTUN import (
|
||||
SING_TUN_DEFAULTS,
|
||||
SING_TUN_HOST_DEFAULTS,
|
||||
SingTUNUnsupportedSettingsError,
|
||||
prepareSingTUNSettings,
|
||||
)
|
||||
from Furious.Repository import Storage
|
||||
@@ -55,6 +56,18 @@ import copy
|
||||
import json
|
||||
|
||||
|
||||
def _unsupportedSettingsErrorText(error: SingTUNUnsupportedSettingsError):
|
||||
"""Translate application validation categories at the presentation boundary."""
|
||||
reason = SingTUNUnsupportedSettingsError.Reason
|
||||
|
||||
return {
|
||||
reason.SettingsFields: _('Unsupported sing-tun settings fields'),
|
||||
reason.Stack: _('Unsupported sing-tun stack'),
|
||||
reason.NativeOption: _('Unsupported or application-owned native option'),
|
||||
reason.HostOptions: _('Unsupported sing-tun host options'),
|
||||
}[error.reason]
|
||||
|
||||
|
||||
class SingTUNSettingsDialog(AppQTransientDialog):
|
||||
"""Never import the native engine or mutate storage while loading widgets."""
|
||||
|
||||
@@ -337,7 +350,7 @@ class SingTUNSettingsDialog(AppQTransientDialog):
|
||||
instructions.setWordWrap(True)
|
||||
advanced.addRow(instructions)
|
||||
|
||||
self.errorLabel = AppQLabel('')
|
||||
self.errorLabel = AppQLabel('', translatable=False)
|
||||
self.errorLabel.setWordWrap(True)
|
||||
|
||||
layout.addWidget(self.errorLabel)
|
||||
@@ -505,7 +518,14 @@ class SingTUNSettingsDialog(AppQTransientDialog):
|
||||
|
||||
Storage.replaceSingTUNSettings(document)
|
||||
except (ValueError, TypeError, OSError) as ex:
|
||||
self.errorLabel.setText(str(ex))
|
||||
self.errorLabel.translatable = isinstance(
|
||||
ex, SingTUNUnsupportedSettingsError
|
||||
)
|
||||
self.errorLabel.setText(
|
||||
_unsupportedSettingsErrorText(ex)
|
||||
if self.errorLabel.translatable
|
||||
else str(ex)
|
||||
)
|
||||
|
||||
return
|
||||
|
||||
|
||||
+104
-1
@@ -26,7 +26,11 @@ from Furious.Core.MultiprocessingRuntime import (
|
||||
)
|
||||
from Furious.Interface import CoreRuntime, RuntimeState, RuntimeExitReason
|
||||
from Furious.Frozenlib import AppSettings, Mixins
|
||||
from Furious.Models.SingTUN import prepareSingTUNSettings, addSingTUNExclusions
|
||||
from Furious.Models.SingTUN import (
|
||||
prepareSingTUNSettings,
|
||||
addSingTUNExclusions,
|
||||
SingTUNUnsupportedSettingsError,
|
||||
)
|
||||
from Furious.Frozenlib.SocksProxy import socksURL
|
||||
from Furious.Repository import Storage
|
||||
from Furious.Repository.SingTUNSettings import UserSingTUNSettings
|
||||
@@ -594,6 +598,105 @@ class SingTUNUIAndStorageTest(unittest.TestCase):
|
||||
processQtEvents()
|
||||
self.assertFalse(isValid(dialog))
|
||||
|
||||
def testUnsupportedSettingsMessagesAreLocalizedAndRetranslatable(self):
|
||||
"""Reject unknown options with localized text without committing settings."""
|
||||
cases = (
|
||||
({'unknown': 1}, 'Unsupported sing-tun settings fields'),
|
||||
(
|
||||
{'tun_options': {'unknown': True}},
|
||||
'Unsupported or application-owned native option',
|
||||
),
|
||||
(
|
||||
{'stack_options': {'unknown': True}},
|
||||
'Unsupported or application-owned native option',
|
||||
),
|
||||
({'host_options': {'sadfasfd': 1}}, 'Unsupported sing-tun host options'),
|
||||
({}, 'Unsupported sing-tun stack'),
|
||||
)
|
||||
|
||||
for advanced, source in cases:
|
||||
with self.subTest(source=source, advanced=advanced), isolatedSettings():
|
||||
AppSettings.set('Language', 'ZH')
|
||||
dialog = self._dialog({})
|
||||
dialog.advanced.setPlainText(json.dumps(advanced))
|
||||
|
||||
if source == 'Unsupported sing-tun stack':
|
||||
dialog.fields['stack'].addItem('unknown', 'unknown')
|
||||
dialog.fields['stack'].setCurrentIndex(
|
||||
dialog.fields['stack'].count() - 1
|
||||
)
|
||||
|
||||
dialog.open()
|
||||
|
||||
try:
|
||||
with mock.patch.object(Storage, 'replaceSingTUNSettings') as commit:
|
||||
dialog.accept()
|
||||
|
||||
commit.assert_not_called()
|
||||
self.assertTrue(dialog.isVisible())
|
||||
self.assertEqual(dialog.errorLabel.text(), _(source, 'ZH'))
|
||||
|
||||
for language in ('ZH', 'RU', 'EN', 'ZH'):
|
||||
AppSettings.set('Language', language)
|
||||
Mixins.QTranslatable.retranslateAll()
|
||||
processQtEvents()
|
||||
|
||||
expected = _(source, language)
|
||||
self.assertEqual(dialog.errorLabel.text(), expected)
|
||||
|
||||
if language != 'EN':
|
||||
self.assertNotEqual(expected, source)
|
||||
finally:
|
||||
dialog.reject()
|
||||
processQtEvents()
|
||||
|
||||
def testUnsupportedErrorCategoryDoesNotDependOnDiagnosticWording(self):
|
||||
"""Keep UI language keyed to the category rather than an English match."""
|
||||
with isolatedSettings():
|
||||
AppSettings.set('Language', 'ZH')
|
||||
dialog = self._dialog({})
|
||||
error = SingTUNUnsupportedSettingsError(
|
||||
SingTUNUnsupportedSettingsError.Reason.HostOptions
|
||||
)
|
||||
error.args = ('changed diagnostic wording',)
|
||||
|
||||
try:
|
||||
with mock.patch.object(dialog, 'document', side_effect=error):
|
||||
dialog.accept()
|
||||
|
||||
self.assertEqual(
|
||||
dialog.errorLabel.text(),
|
||||
_('Unsupported sing-tun host options', 'ZH'),
|
||||
)
|
||||
finally:
|
||||
dialog.reject()
|
||||
processQtEvents()
|
||||
|
||||
def testOtherErrorsKeepTheirDiagnosticsAcrossLanguageChanges(self):
|
||||
"""Binding and persistence diagnostics do not become category whitelists."""
|
||||
for error in (
|
||||
ValueError('Unsupported sing-tun host options'),
|
||||
TypeError('arbitrary diagnostic detail'),
|
||||
OSError('unable to save settings'),
|
||||
):
|
||||
with self.subTest(error=error), isolatedSettings():
|
||||
AppSettings.set('Language', 'ZH')
|
||||
dialog = self._dialog({})
|
||||
|
||||
try:
|
||||
with mock.patch.object(dialog, 'document', side_effect=error):
|
||||
dialog.accept()
|
||||
|
||||
for language in ('ZH', 'RU', 'EN'):
|
||||
AppSettings.set('Language', language)
|
||||
Mixins.QTranslatable.retranslateAll()
|
||||
processQtEvents()
|
||||
|
||||
self.assertEqual(dialog.errorLabel.text(), str(error))
|
||||
finally:
|
||||
dialog.reject()
|
||||
processQtEvents()
|
||||
|
||||
def testSelectiveRowsKeepInputsBoundedAndPreserveKeyboardOrderAndValues(self):
|
||||
pages = (
|
||||
(
|
||||
|
||||
Reference in New Issue
Block a user