Update XbmcContext.localize to handle string interpolation #1225

- Allows for exception handling in the event that any translation file is out of sync with EN_GB
This commit is contained in:
MoojMidge 2025-07-23 22:30:30 +09:00
parent dfd0b11d9e
commit 89b5f4ff24
16 changed files with 119 additions and 100 deletions

View file

@ -148,8 +148,8 @@ class AbstractProvider(object):
try:
if wizard_steps and ui.on_yes_no_input(
' - '.join((localize('youtube'), localize('setup_wizard'))),
(localize('setup_wizard.prompt.x')
% localize('setup_wizard.prompt.settings'))
localize(('setup_wizard.prompt.x',
'setup_wizard.prompt.settings')),
):
for wizard_step in wizard_steps:
if callable(wizard_step):
@ -422,14 +422,14 @@ class AbstractProvider(object):
query = to_unicode(params.get('q', ''))
if not ui.on_yes_no_input(
localize('content.remove'),
localize('content.remove.check.x') % query,
localize('content.remove.check.x', query),
):
return False, None
search_history.del_item(query)
ui.refresh_container()
ui.show_notification(localize('removed.name.x') % query,
ui.show_notification(localize('removed.name.x', query),
time_ms=2500,
audible=False)
return True, None
@ -450,8 +450,7 @@ class AbstractProvider(object):
if command == 'clear':
if not ui.on_yes_no_input(
localize('search.clear'),
localize('content.clear.check.x')
% localize('search.history')
localize(('content.clear.check.x', 'search.history'))
):
return False, None

View file

@ -566,7 +566,7 @@ class AbstractContext(object):
def get_settings(self, refresh=False):
raise NotImplementedError()
def localize(self, text_id, default_text=None):
def localize(self, text_id, args=None, default_text=None):
raise NotImplementedError()
def apply_content(self,

View file

@ -614,30 +614,63 @@ class XbmcContext(AbstractContext):
self.__class__._settings = XbmcPluginSettings(addon)
return self._settings
def localize(self, text_id, default_text=None):
if default_text is None:
default_text = 'Undefined string ID: {0!r}'.format(text_id)
def localize(self, text_id, args=None, default_text=None):
if isinstance(text_id, tuple):
_args = text_id[1:]
_text_id = text_id[0]
localize_args = True
else:
_args = args
_text_id = text_id
localize_args = False
if not isinstance(text_id, int):
if not isinstance(_text_id, int):
try:
text_id = self.LOCAL_MAP[text_id]
_text_id = self.LOCAL_MAP[_text_id]
except KeyError:
try:
text_id = int(text_id)
_text_id = int(_text_id)
except ValueError:
return default_text
if text_id <= 0:
_text_id = -1
if _text_id <= 0:
msg = 'Undefined string ID: {text_id!r}'
if default_text is None:
default_text = msg.format(text_id=text_id)
self.log.warning(msg)
else:
self.log.warning(msg, text_id=text_id)
return default_text
"""
We want to use all localization strings!
Addons should only use the range 30000 thru 30999
(see: http://kodi.wiki/view/Language_support) but we do it anyway.
Addons should only use the range 30000 through 30999
(see: http://kodi.wiki/view/Language_support), but we do it anyway.
I want some of the localized strings for the views of a skin.
"""
source = self._addon if 30000 <= text_id < 31000 else xbmc
result = source.getLocalizedString(text_id)
result = to_unicode(result) if result else default_text
source = self._addon if 30000 <= _text_id < 31000 else xbmc
result = source.getLocalizedString(_text_id)
if not result:
msg = 'Untranslated string ID: {text_id!r}'
if default_text is None:
default_text = msg.format(text_id=text_id)
self.log.warning(msg)
else:
self.log.warning(msg, text_id=text_id)
return default_text
result = to_unicode(result)
if _args:
if localize_args:
_args = tuple(self.localize(arg, default_text=arg)
for arg in _args)
try:
return result % _args
except TypeError:
self.log.exception(('Localization error',
'text_id: {text_id!r}',
'args: {original_args!r}'),
text_id=text_id,
original_args=args)
return result
def apply_content(self,

View file

@ -384,8 +384,7 @@ def history_list_assign(context, playlist_id, playlist_name):
def my_subscriptions_filter_remove(context, channel_name):
return (
context.localize('remove.from.x')
% context.localize('my_subscriptions.filtered'),
context.localize(('remove.from.x', 'my_subscriptions.filtered')),
context_menu_uri(
context,
('my_subscriptions', 'filter', 'remove'),
@ -398,8 +397,7 @@ def my_subscriptions_filter_remove(context, channel_name):
def my_subscriptions_filter_add(context, channel_name):
return (
context.localize('add.to.x')
% context.localize('my_subscriptions.filtered'),
context.localize(('add.to.x', 'my_subscriptions.filtered')),
context_menu_uri(
context,
('my_subscriptions', 'filter', 'add',),
@ -467,7 +465,7 @@ def watch_later_local_clear(context):
def channel_go_to(context, channel_id, channel_name):
return (
context.localize('go_to.x') % context.get_ui().bold(channel_name),
context.localize('go_to.x', context.get_ui().bold(channel_name)),
context_menu_uri(
context,
(PATHS.ROUTE, PATHS.CHANNEL, channel_id,),
@ -477,7 +475,7 @@ def channel_go_to(context, channel_id, channel_name):
def channel_subscribe_to(context, channel_id, channel_name=''):
return (
context.localize('subscribe_to.x') % context.get_ui().bold(channel_name)
context.localize('subscribe_to.x', context.get_ui().bold(channel_name))
if channel_name else
context.localize('subscribe'),
context_menu_uri(
@ -661,10 +659,10 @@ def bookmark_add(context, item):
def bookmark_add_channel(context, channel_id, channel_name=''):
return (
(context.localize('bookmark.x') % (
context.get_ui().bold(channel_name) if channel_name else
context.localize('channel')
)),
context.localize('bookmark.x',
context.get_ui().bold(channel_name)
if channel_name else
context.localize('channel')),
context_menu_uri(
context,
(PATHS.BOOKMARKS, 'add',),
@ -678,7 +676,7 @@ def bookmark_add_channel(context, channel_id, channel_name=''):
def bookmark_edit(context, item_id, item_name, item_uri):
return (
context.localize('edit.x') % context.localize('bookmark'),
context.localize(('edit.x', 'bookmark')),
context_menu_uri(
context,
(PATHS.BOOKMARKS, 'edit',),

View file

@ -32,7 +32,7 @@ class NextPageItem(DirectoryItem):
if 'page_token' not in params and can_jump:
params['page_token'] = self.create_page_token(page, items_per_page)
name = context.localize('page.next') % page
name = context.localize('page.next', page)
filtered = params.get('filtered')
if filtered:
name = ''.join((

View file

@ -344,8 +344,7 @@ class APIKeyStore(JSONStore):
updated_list.append(localize('api.secret'))
log_list.append('client_secret')
if updated_list:
ui.show_notification(localize('updated.x')
% ', '.join(updated_list))
ui.show_notification(localize('updated.x', ', '.join(updated_list)))
self.log.debug('Updated API details: %s', log_list)
client_id = settings.api_id()
@ -367,8 +366,8 @@ class APIKeyStore(JSONStore):
if not client_secret:
missing_list.append(localize('api.secret'))
log_list.append('client_secret')
ui.show_notification(localize('api.personal.failed')
% ', '.join(missing_list))
ui.show_notification(localize('api.personal.failed',
', '.join(missing_list)))
self.log.error_trace(('Failed to enable personal API keys',
'Missing: %s'),
log_list)

View file

@ -328,7 +328,7 @@ class RequestHandler(BaseHTTPRequestHandler, object):
if updated:
# Successfully updated
updated = localize('api.config.updated') % ', '.join(updated)
updated = localize('api.config.updated', ', '.join(updated))
else:
# No changes, not updated
updated = localize('api.config.not_updated')

View file

@ -77,8 +77,8 @@ def _config_actions(context, action, *_args):
sub_opts = [
localize('none'),
localize('select'),
localize('subtitles.with_fallback') % (preferred, fallback),
localize('subtitles.with_fallback') % (preferred_no_asr, fallback),
localize('subtitles.with_fallback', (preferred, fallback)),
localize('subtitles.with_fallback', (preferred_no_asr, fallback)),
preferred_no_asr,
]
@ -119,7 +119,7 @@ def _config_actions(context, action, *_args):
client_ip = get_client_ip_address(context)
if client_ip:
ui.on_ok(context.get_name(),
context.localize('client.ip.is.x') % client_ip)
context.localize('client.ip.is.x', client_ip))
else:
ui.show_notification(context.localize('client.ip.failed'))
else:
@ -376,8 +376,8 @@ def _user_actions(context, action, params):
def switch_to_user(user):
access_manager.set_user(user, switch_to=True)
ui.show_notification(localize('user.changed_to.x')
% access_manager.get_username(user),
ui.show_notification(localize('user.changed_to.x',
access_manager.get_username(user)),
localize('user.switch'))
if action == 'switch':
@ -399,7 +399,7 @@ def _user_actions(context, action, params):
if user is not None:
result = ui.on_yes_no_input(
localize('user.switch'),
localize('user.switch_to.x') % details.get('name')
localize('user.switch_to.x', details.get('name'))
)
if result:
switch_to_user(user)
@ -414,7 +414,7 @@ def _user_actions(context, action, params):
username = access_manager.get_username(user)
if ui.on_remove_content(username):
access_manager.remove_user(user)
ui.show_notification(localize('removed.name.x') % username,
ui.show_notification(localize('removed.name.x', username),
localize('remove'))
if user == 0:
access_manager.add_user(username=localize('user.default'),
@ -441,8 +441,8 @@ def _user_actions(context, action, params):
return False
if access_manager.set_username(user, new_username):
ui.show_notification(localize('renamed.x.y')
% (old_username, new_username),
ui.show_notification(localize('renamed.x.y',
(old_username, new_username)),
localize('rename'))
reload = True

View file

@ -81,19 +81,19 @@ class XbmcContextUI(AbstractContextUI):
def on_remove_content(self, name):
return self.on_yes_no_input(
self._context.localize('content.remove'),
self._context.localize('content.remove.check.x') % to_unicode(name),
self._context.localize('content.remove.check.x', to_unicode(name)),
)
def on_delete_content(self, name):
return self.on_yes_no_input(
self._context.localize('content.delete'),
self._context.localize('content.delete.check.x') % to_unicode(name),
self._context.localize('content.delete.check.x', to_unicode(name)),
)
def on_clear_content(self, name):
return self.on_yes_no_input(
self._context.localize('content.clear'),
self._context.localize('content.clear.check.x') % to_unicode(name),
self._context.localize('content.clear.check.x', to_unicode(name)),
)
def on_select(self, title, items=None, preselect=-1, use_details=False):

View file

@ -2698,14 +2698,13 @@ class PlayerClient(LoginClient):
set_id += 1
if subs_data:
translation_lang = localize('subtitles.translation.x')
headers = subs_data.pop('_headers', None)
for lang_id, subtitle in subs_data.items():
lang_code = subtitle['lang']
label = language = subtitle['language']
kind = subtitle['kind']
if kind == 'translation':
label = translation_lang % language
label = localize('subtitles.translation.x', language)
kind = '_'.join((lang_code, kind))
else:
kind = lang_id

View file

@ -374,11 +374,12 @@ class Subtitles(object):
if not num_total:
self.log.debug('No subtitles found for prompt')
else:
translation_lang = self._context.localize('subtitles.translation.x')
localize = self._context.localize
choice = self._context.get_ui().on_select(
self._context.localize('subtitles.language'),
localize('subtitles.language'),
[name for _, name in captions] +
[translation_lang % name for _, name in translations]
[localize('subtitles.translation.x', name)
for _, name in translations]
)
if 0 <= choice < num_captions:

View file

@ -76,7 +76,7 @@ def _do_login(provider, context, client=None, **kwargs):
verification_url = 'youtube.com/activate'
message = ''.join((
localize('sign.go_to') % ui.bold(verification_url),
localize('sign.go_to', ui.bold(verification_url)),
'[CR]',
localize('sign.enter_code'),
' ',

View file

@ -57,16 +57,14 @@ def _process_add_video(provider, context):
logging.debug('Playlist/Add: failed for playlist {playlist_id!r}'
.format(playlist_id=playlist_id))
ui.show_notification(
message=(localize('failed.x')
% localize('add.to.x')
% localize('playlist')),
message=localize(('failed.x', ('add.to.x', 'playlist'))),
time_ms=2500,
audible=False,
)
return False
ui.show_notification(
message=localize('added.to.x') % localize('playlist'),
message=localize(('added.to.x', 'playlist')),
time_ms=2500,
audible=False,
)
@ -134,9 +132,7 @@ def _process_remove_video(provider,
)
if not success:
ui.show_notification(
message=(localize('failed.x')
% localize('remove.from.x')
% localize('playlist')),
message=localize(('failed.x', ('remove.from.x', 'playlist'))),
time_ms=2500,
audible=False,
)
@ -144,7 +140,7 @@ def _process_remove_video(provider,
if not confirmed:
ui.show_notification(
message=localize('removed.from.x') % localize('playlist'),
message=localize(('removed.from.x', 'playlist')),
time_ms=2500,
audible=False,
)
@ -198,16 +194,14 @@ def _process_remove_playlist(provider, context):
success = provider.get_client(context).remove_playlist(playlist_id)
if not success:
ui.show_notification(
message=(localize('failed.x')
% localize('remove.x')
% localize('playlist')),
message=localize(('failed.x', ('remove.x', 'playlist'))),
time_ms=2500,
audible=False,
)
return False
ui.show_notification(
message=localize('removed.name.x') % playlist_name,
message=localize('removed.name.x', playlist_name),
time_ms=2500,
audible=False,
)
@ -323,7 +317,7 @@ def _process_select_playlist(provider, context):
if page_token:
next_page = current_page + 1
items.append((
ui.bold(context.localize('page.next') % next_page), '',
ui.bold(context.localize('page.next', next_page)), '',
'playlist.next',
'DefaultFolder.png',
))
@ -378,9 +372,7 @@ def _process_rename_playlist(provider, context):
)
if not success:
ui.show_notification(
message=(localize('failed.x')
% localize('rename')
% localize('playlist')),
message=localize(('failed.x', ('rename', 'playlist'))),
time_ms=2500,
audible=False,
)
@ -420,7 +412,7 @@ def _playlist_id_change(context, playlist, command):
context.get_name(),
context.localize('{type}.list.{command}.check'.format(
type=playlist, command=command
)) % playlist_name
), playlist_name),
):
if command == 'unassign':
playlist_id = None
@ -472,7 +464,7 @@ def _process_rate_playlist(provider,
ui.show_notification(
message=(localize('saved')
if rating == 'like' else
localize('removed.name.x') % playlist_name),
localize('removed.name.x', playlist_name)),
time_ms=2500,
audible=False,
)
@ -500,8 +492,7 @@ def _process_rate_playlist(provider,
elif success is False:
ui.show_notification(
message=(localize('failed.x')
% localize('save')
message=(localize(('failed.x', 'save'))
if rating == 'like' else
localize('remove')),
time_ms=2500,

View file

@ -36,8 +36,7 @@ def process_language(context, step, steps, **_kwargs):
step=step,
steps=steps,
),
(localize('setup_wizard.prompt.x')
% localize('setup_wizard.prompt.locale'))
localize(('setup_wizard.prompt.x', 'setup_wizard.prompt.locale')),
):
context.execute(
'RunScript({addon_id},config/language_region)'.format(
@ -60,8 +59,8 @@ def process_geo_location(context, step, steps, **_kwargs):
step=step,
steps=steps,
),
(localize('setup_wizard.prompt.x')
% localize('setup_wizard.prompt.my_location'))
localize(('setup_wizard.prompt.x',
'setup_wizard.prompt.my_location')),
):
context.execute(
'RunScript({addon_id},config/geo_location)'.format(
@ -86,8 +85,8 @@ def process_default_settings(context, step, steps, **_kwargs):
step=step,
steps=steps,
),
(localize('setup_wizard.prompt.x')
% localize('setup_wizard.prompt.settings.defaults'))
localize(('setup_wizard.prompt.x',
'setup_wizard.prompt.settings.defaults')),
):
settings.use_isa(True)
settings.use_mpd_videos(True)
@ -143,8 +142,8 @@ def process_list_detail_settings(context, step, steps, **_kwargs):
step=step,
steps=steps,
),
(localize('setup_wizard.prompt.x')
% localize('setup_wizard.prompt.settings.list_details'))
localize(('setup_wizard.prompt.x',
'setup_wizard.prompt.settings.list_details')),
):
settings.show_detailed_description(False)
settings.show_detailed_labels(False)
@ -167,8 +166,8 @@ def process_performance_settings(context, step, steps, **_kwargs):
step=step,
steps=steps,
),
(localize('setup_wizard.prompt.x')
% localize('setup_wizard.prompt.settings.performance'))
localize(('setup_wizard.prompt.x',
'setup_wizard.prompt.settings.performance')),
):
device_types = {
'720p30': {
@ -247,8 +246,8 @@ def process_subtitles(context, step, steps, **_kwargs):
step=step,
steps=steps,
),
(localize('setup_wizard.prompt.x')
% localize('setup_wizard.prompt.subtitles'))
localize(('setup_wizard.prompt.x',
'setup_wizard.prompt.subtitles')),
):
context.execute(
'RunScript({addon_id},config/subtitles)'.format(

View file

@ -81,7 +81,7 @@ def _process_rate_video(provider,
ui.refresh_container()
if result == 'none':
notify_message = localize('removed.x') % localize('rating')
notify_message = localize(('removed.x', 'rating'))
elif result == 'like':
notify_message = localize('liked.video')
elif result == 'dislike':

View file

@ -1141,10 +1141,10 @@ class Provider(AbstractProvider):
])
settings.subscriptions_filter(filter_list)
ui.show_notification(context.localize('added.to.x'
if command == 'add' else
'removed.from.x')
% context.localize('my_subscriptions.filtered'))
ui.show_notification(context.localize(('added.to.x'
if command == 'add' else
'removed.from.x',
'my_subscriptions.filtered')))
return True, None
@AbstractProvider.register_path(
@ -1264,7 +1264,7 @@ class Provider(AbstractProvider):
video_name = to_unicode(video_name)
if not ui.on_yes_no_input(
localize('content.remove'),
localize('content.remove.check.x') % video_name,
localize('content.remove.check.x', video_name),
):
return False, {provider.FALLBACK: False}
@ -1272,7 +1272,7 @@ class Provider(AbstractProvider):
ui.refresh_container()
ui.show_notification(
localize('removed.name.x') % video_name,
localize('removed.name.x', video_name),
time_ms=2500,
audible=False,
)
@ -1956,7 +1956,7 @@ class Provider(AbstractProvider):
ui.refresh_container()
ui.show_notification(
localize('updated.x') % item_name
localize('updated.x', item_name)
if item_id else
localize('bookmark.created'),
time_ms=2500,
@ -1988,7 +1988,7 @@ class Provider(AbstractProvider):
bookmark_name = to_unicode(bookmark_name)
if not ui.on_yes_no_input(
localize('content.remove'),
localize('content.remove.check.x') % bookmark_name,
localize('content.remove.check.x', bookmark_name),
):
return False, {provider.FALLBACK: False}
@ -1996,7 +1996,7 @@ class Provider(AbstractProvider):
ui.refresh_container()
ui.show_notification(
localize('removed.name.x') % bookmark_name,
localize('removed.name.x', bookmark_name),
time_ms=2500,
audible=False,
)
@ -2084,7 +2084,7 @@ class Provider(AbstractProvider):
context.get_watch_later_list().add_item(video_id, item)
ui.show_notification(
localize('added.to.x') % localize('watch_later'),
localize(('added.to.x', 'watch_later')),
time_ms=2500,
audible=False,
)
@ -2095,7 +2095,7 @@ class Provider(AbstractProvider):
video_name = to_unicode(video_name)
if not ui.on_yes_no_input(
localize('content.remove'),
localize('content.remove.check.x') % video_name,
localize('content.remove.check.x', video_name),
):
return False, {provider.FALLBACK: False}
@ -2103,7 +2103,7 @@ class Provider(AbstractProvider):
ui.refresh_container()
ui.show_notification(
localize('removed.name.x') % video_name,
localize('removed.name.x', video_name),
time_ms=2500,
audible=False,
)