anonstream/anonstream/utils/locale.py

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)