145 行
4.4 KiB
Python
145 行
4.4 KiB
Python
import types
|
|
|
|
SPEC = {
|
|
'anonstream': {
|
|
'error': {
|
|
'invalid_token': str,
|
|
'captcha': str,
|
|
'captcha_again': str,
|
|
'impostor': str,
|
|
'broadcaster_should_log_in': str,
|
|
'wrong_username_or_password': str,
|
|
'blacklisted': str,
|
|
'not_whitelisted': str,
|
|
'offline': str,
|
|
'ratelimit': str,
|
|
'limit': str,
|
|
},
|
|
'internal': {
|
|
'captcha_required': str,
|
|
'captcha_incorrect': str,
|
|
'captcha_expired': str,
|
|
'message_ratelimited': str,
|
|
'message_suspected_duplicate': str,
|
|
'message_empty': str,
|
|
'message_practically_empty': str,
|
|
'message_too_long': str,
|
|
'message_too_many_lines': str,
|
|
'message_too_many_apparent_lines': str,
|
|
'appearance_changed': str,
|
|
'name_empty': str,
|
|
'name_too_long': str,
|
|
'colour_invalid_css': str,
|
|
'colour_insufficient_contrast': str,
|
|
'password_too_long': str,
|
|
},
|
|
'captcha': {
|
|
'captcha_failed_to_load': str,
|
|
'click_for_a_new_captcha': str,
|
|
},
|
|
'home': {
|
|
'info': str,
|
|
'chat': str,
|
|
'both': str,
|
|
'source': str,
|
|
'users': str,
|
|
'users_in_chat': str,
|
|
'stream_chat': str,
|
|
},
|
|
'stream': {
|
|
'offline': str,
|
|
},
|
|
'info': {
|
|
'viewers': str,
|
|
'uptime': str,
|
|
'reload_stream': str,
|
|
},
|
|
'chat': {
|
|
'users': str,
|
|
'click_to_refresh': str,
|
|
'hide_timeout_notice': str,
|
|
'watching': str,
|
|
'not_watching': str,
|
|
'you': str,
|
|
'timed_out': str,
|
|
},
|
|
'form': {
|
|
'click_to_dismiss': str,
|
|
'send_a_message': str,
|
|
'captcha': str,
|
|
'settings': str,
|
|
'captcha_failed_to_load': str,
|
|
'click_for_a_new_captcha': str,
|
|
'chat': str,
|
|
'name': str,
|
|
'tripcode': str,
|
|
'no_tripcode': str,
|
|
'set': str,
|
|
'cleared': str,
|
|
'undo': str,
|
|
'tripcode_password': str,
|
|
'return_to_chat': str,
|
|
'update': str,
|
|
},
|
|
'js': {
|
|
'offline': str,
|
|
'reload_stream': str,
|
|
'chat_scroll_paused': str,
|
|
'not_connected': str,
|
|
'broadcaster': str,
|
|
'loading': str,
|
|
'click_for_a_new_captcha': str,
|
|
'viewers': str,
|
|
'you': str,
|
|
'watching': str,
|
|
'not_watching': str,
|
|
'errors': str,
|
|
'connecting_to_chat': str,
|
|
'connected_to_chat': str,
|
|
'disconnected_from_chat': str,
|
|
'error_connecting_to_chat': str,
|
|
'error_connecting_to_chat_terse': str,
|
|
}
|
|
},
|
|
'http': {
|
|
'400': str | None,
|
|
'401': str | None,
|
|
'403': str | None,
|
|
'404': str | None,
|
|
'405': str | None,
|
|
'410': str | None,
|
|
'500': str | None,
|
|
}
|
|
}
|
|
|
|
class Nonconforming(Exception):
|
|
pass
|
|
|
|
def _conform_to_spec(data, spec, level=()):
|
|
assert isinstance(spec, dict), \
|
|
f'bad locale spec at {level}: must be {dict}, not {type(spec)}'
|
|
if not isinstance(data, dict):
|
|
raise Nonconforming(
|
|
f'object at {level} must be dict, not {type(data)}'
|
|
)
|
|
missing_keys = set(spec.keys()) - set(data.keys())
|
|
if missing_keys:
|
|
raise Nonconforming(f'dict at {level} is missing keys {missing_keys}')
|
|
extra_keys = set(data.keys()) - set(spec.keys())
|
|
if extra_keys:
|
|
raise Nonconforming(f'dict at {level} has extra keys {extra_keys}')
|
|
for key, subspec in spec.items():
|
|
subdata = data[key]
|
|
if isinstance(subspec, dict):
|
|
_conform_to_spec(subdata, subspec, level + (key,))
|
|
else:
|
|
assert isinstance(subspec, type | types.UnionType), \
|
|
f'bad locale spec at {level + (key,)}: must be {dict | type}, not {type(subspec)}'
|
|
if not isinstance(subdata, subspec):
|
|
raise Nonconforming(
|
|
f'value at {level + (key,)} must be {subspec}, not {type(subdata)}'
|
|
)
|
|
|
|
def validate_locale(locale):
|
|
return _conform_to_spec(locale, SPEC)
|