Added translate status feature (requires server side support)

pull/268/head
Daniel Schwarz 2022-12-07 19:57:56 -05:00 zatwierdzone przez Ivan Habunek
rodzic f9fef1927c
commit be5948bac8
Nie znaleziono w bazie danych klucza dla tego podpisu
ID klucza GPG: F5F0623FF5EBCB3D
4 zmienionych plików z 75 dodań i 8 usunięć

Wyświetl plik

@ -211,6 +211,10 @@ def unbookmark(app, user, status_id):
return _status_action(app, user, status_id, 'unbookmark') return _status_action(app, user, status_id, 'unbookmark')
def translate(app, user, status_id):
return _status_action(app, user, status_id, 'translate')
def context(app, user, status_id): def context(app, user, status_id):
url = '/api/v1/statuses/{}/context'.format(status_id) url = '/api/v1/statuses/{}/context'.format(status_id)

Wyświetl plik

@ -106,6 +106,7 @@ class TUI(urwid.Frame):
self.timeline = None self.timeline = None
self.overlay = None self.overlay = None
self.exception = None self.exception = None
self.can_translate = False
super().__init__(self.body, header=self.header, footer=self.footer) super().__init__(self.body, header=self.header, footer=self.footer)
@ -206,6 +207,7 @@ class TUI(urwid.Frame):
urwid.connect_signal(timeline, "source", _source) urwid.connect_signal(timeline, "source", _source)
urwid.connect_signal(timeline, "links", _links) urwid.connect_signal(timeline, "links", _links)
urwid.connect_signal(timeline, "zoom", _zoom) urwid.connect_signal(timeline, "zoom", _zoom)
urwid.connect_signal(timeline, "translate", self.async_translate)
def build_timeline(self, name, statuses, local): def build_timeline(self, name, statuses, local):
def _close(*args): def _close(*args):
@ -232,7 +234,7 @@ class TUI(urwid.Frame):
self.loop.set_alarm_in(5, lambda *args: self.footer.clear_message()) self.loop.set_alarm_in(5, lambda *args: self.footer.clear_message())
config.save_config(self.config) config.save_config(self.config)
timeline = Timeline(name, statuses) timeline = Timeline(name, statuses, self.can_translate)
self.connect_default_timeline_signals(timeline) self.connect_default_timeline_signals(timeline)
urwid.connect_signal(timeline, "next", _next) urwid.connect_signal(timeline, "next", _next)
@ -261,8 +263,8 @@ class TUI(urwid.Frame):
statuses = ancestors + [status] + descendants statuses = ancestors + [status] + descendants
focus = len(ancestors) focus = len(ancestors)
timeline = Timeline("thread", statuses, focus, is_thread=True) timeline = Timeline("thread", statuses, self.can_translate, focus,
is_thread=True)
self.connect_default_timeline_signals(timeline) self.connect_default_timeline_signals(timeline)
urwid.connect_signal(timeline, "close", _close) urwid.connect_signal(timeline, "close", _close)
@ -303,6 +305,11 @@ class TUI(urwid.Frame):
Attempt to update max_toot_chars from instance data. Attempt to update max_toot_chars from instance data.
Does not work on vanilla Mastodon, works on Pleroma. Does not work on vanilla Mastodon, works on Pleroma.
See: https://github.com/tootsuite/mastodon/issues/4915 See: https://github.com/tootsuite/mastodon/issues/4915
Also attempt to update translation flag from instance
data. Translation is only present on Mastodon 4+ servers
where the administrator has enabled this feature.
See: https://github.com/mastodon/mastodon/issues/19328
""" """
def _load_instance(): def _load_instance():
return api.get_instance(self.app.instance) return api.get_instance(self.app.instance)
@ -310,6 +317,17 @@ class TUI(urwid.Frame):
def _done(instance): def _done(instance):
if "max_toot_chars" in instance: if "max_toot_chars" in instance:
self.max_toot_chars = instance["max_toot_chars"] self.max_toot_chars = instance["max_toot_chars"]
if "translation" in instance:
# instance is advertising translation service
self.can_translate = instance["translation"]["enabled"]
else:
if "version" in instance:
# fallback check:
# get the major version number of the server
# this works for Mastodon and Pleroma version strings
# Mastodon versions < 4 do not have translation service
# Revisit this logic if Pleroma implements translation
self.can_translate = int(instance["version"][0]) > 3
return self.run_in_thread(_load_instance, done_callback=_done) return self.run_in_thread(_load_instance, done_callback=_done)
@ -484,6 +502,37 @@ class TUI(urwid.Frame):
done_callback=_done done_callback=_done
) )
def async_translate(self, timeline, status):
def _translate():
logger.info("Translating {}".format(status))
self.footer.set_message("Translating status {}".format(status.id))
try:
response = api.translate(self.app, self.user, status.id)
except:
response = None
finally:
self.footer.clear_message()
return response
def _done(response):
if response is not None:
# Create a new Status that is translated
new_data = status.data
new_data["content"] = response["content"]
new_data["detected_source_language"] = response["detected_source_language"]
new_status = self.make_status(new_data)
timeline.update_status(new_status)
self.footer.set_message(f"Translated status {status.id} from {response['detected_source_language']}")
else:
self.footer.set_error_message("Translate server error")
self.loop.set_alarm_in(5, lambda *args: self.footer.clear_message())
self.run_in_thread(_translate, done_callback=_done )
def async_delete_status(self, timeline, status): def async_delete_status(self, timeline, status):
def _delete(): def _delete():
api.delete_status(self.app, self.user, status.id) api.delete_status(self.app, self.user, status.id)

Wyświetl plik

@ -164,6 +164,7 @@ class Help(urwid.Padding):
yield urwid.Text(h(" [B] - Boost/unboost status")) yield urwid.Text(h(" [B] - Boost/unboost status"))
yield urwid.Text(h(" [C] - Compose new status")) yield urwid.Text(h(" [C] - Compose new status"))
yield urwid.Text(h(" [F] - Favourite/unfavourite status")) yield urwid.Text(h(" [F] - Favourite/unfavourite status"))
yield urwid.Text(h(" [N] - Translate status, if possible"))
yield urwid.Text(h(" [R] - Reply to current status")) yield urwid.Text(h(" [R] - Reply to current status"))
yield urwid.Text(h(" [S] - Show text marked as sensitive")) yield urwid.Text(h(" [S] - Show text marked as sensitive"))
yield urwid.Text(h(" [T] - Show status thread (replies)")) yield urwid.Text(h(" [T] - Show status thread (replies)"))

Wyświetl plik

@ -28,19 +28,21 @@ class Timeline(urwid.Columns):
"source", # Show status source "source", # Show status source
"links", # Show status links "links", # Show status links
"thread", # Show thread for status "thread", # Show thread for status
"translate", # Translate status
"save", # Save current timeline "save", # Save current timeline
"zoom", # Open status in scrollable popup window "zoom", # Open status in scrollable popup window
] ]
def __init__(self, name, statuses, focus=0, is_thread=False): def __init__(self, name, statuses, can_translate, focus=0, is_thread=False):
self.name = name self.name = name
self.is_thread = is_thread self.is_thread = is_thread
self.statuses = statuses self.statuses = statuses
self.can_translate = can_translate
self.status_list = self.build_status_list(statuses, focus=focus) self.status_list = self.build_status_list(statuses, focus=focus)
try: try:
self.status_details = StatusDetails(statuses[focus], is_thread) self.status_details = StatusDetails(statuses[focus], is_thread, can_translate)
except IndexError: except IndexError:
self.status_details = StatusDetails(None, is_thread) self.status_details = StatusDetails(None, is_thread, can_translate)
super().__init__([ super().__init__([
("weight", 40, self.status_list), ("weight", 40, self.status_list),
@ -97,7 +99,7 @@ class Timeline(urwid.Columns):
self.draw_status_details(status) self.draw_status_details(status)
def draw_status_details(self, status): def draw_status_details(self, status):
self.status_details = StatusDetails(status, self.is_thread) self.status_details = StatusDetails(status, self.is_thread, self.can_translate)
self.contents[2] = urwid.Padding(self.status_details, left=1), ("weight", 60, False) self.contents[2] = urwid.Padding(self.status_details, left=1), ("weight", 60, False)
def keypress(self, size, key): def keypress(self, size, key):
@ -157,6 +159,11 @@ class Timeline(urwid.Columns):
self._emit("links", status) self._emit("links", status)
return return
if key in ("n", "N"):
if self.can_translate:
self._emit("translate", status)
return
if key in ("t", "T"): if key in ("t", "T"):
self._emit("thread", status) self._emit("thread", status)
return return
@ -228,7 +235,7 @@ class Timeline(urwid.Columns):
class StatusDetails(urwid.Pile): class StatusDetails(urwid.Pile):
def __init__(self, status, in_thread): def __init__(self, status, in_thread, can_translate=False):
""" """
Parameters Parameters
---------- ----------
@ -239,6 +246,7 @@ class StatusDetails(urwid.Pile):
Whether the status is rendered from a thread status list. Whether the status is rendered from a thread status list.
""" """
self.in_thread = in_thread self.in_thread = in_thread
self.can_translate = can_translate
reblogged_by = status.author if status and status.reblog else None reblogged_by = status.author if status and status.reblog else None
widget_list = list(self.content_generator(status.original, reblogged_by) widget_list = list(self.content_generator(status.original, reblogged_by)
if status else ()) if status else ())
@ -290,10 +298,14 @@ class StatusDetails(urwid.Pile):
application = application.get("name") application = application.get("name")
yield ("pack", urwid.AttrWrap(urwid.Divider("-"), "gray")) yield ("pack", urwid.AttrWrap(urwid.Divider("-"), "gray"))
translated = status.data.get("detected_source_language")
yield ("pack", urwid.Text([ yield ("pack", urwid.Text([
("gray", "{} ".format(status.data["replies_count"])), ("gray", "{} ".format(status.data["replies_count"])),
("yellow" if status.reblogged else "gray", "{} ".format(status.data["reblogs_count"])), ("yellow" if status.reblogged else "gray", "{} ".format(status.data["reblogs_count"])),
("yellow" if status.favourited else "gray", "{}".format(status.data["favourites_count"])), ("yellow" if status.favourited else "gray", "{}".format(status.data["favourites_count"])),
("yellow" if translated else "gray", " · Translated from {} ".format(translated) if translated else ""),
("gray", " · {}".format(application) if application else ""), ("gray", " · {}".format(application) if application else ""),
])) ]))
@ -310,6 +322,7 @@ class StatusDetails(urwid.Pile):
"[R]eply", "[R]eply",
"So[u]rce", "So[u]rce",
"[Z]oom", "[Z]oom",
"Tra[n]slate" if self.can_translate else "",
"[H]elp", "[H]elp",
] ]
options = " ".join(o for o in options if o) options = " ".join(o for o in options if o)