Skip to content

Commit

Permalink
Deepl API provider and a few fixes (#369)
Browse files Browse the repository at this point in the history
  • Loading branch information
mufeedali authored Apr 14, 2024
2 parents db9d47f + b5933cf commit b490323
Show file tree
Hide file tree
Showing 22 changed files with 591 additions and 176 deletions.
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,11 +49,12 @@ sudo apt-get install dialect
- PyGObject `python-gobject`
- GTK4 `gtk4`
- libadwaita (>= 1.4.0) `libadwaita`
- libsoup (>= 3.0) `libsoup`
- libsecret
- GStreamer 1.0 `gstreamer`
- Meson `meson`
- Ninja `ninja`
- gTTS `python-gtts`
- D-Bus `python-dbus`
- Beautiful Soup `python-beautifulsoup4`

If official packages are not available for any of the python dependencies, you can install them from pip:
Expand Down
19 changes: 19 additions & 0 deletions data/resources/provider-preferences.blp
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,25 @@ template $ProviderPreferences : Adw.NavigationPage {
}
}
}

Adw.PreferencesGroup api_usage_group {
title: _("Character Usage");
visible: false;

Box {
orientation: vertical;
spacing: 3;

LevelBar api_usage {

}

Label api_usage_label {

}
}

}
}
}
}
2 changes: 1 addition & 1 deletion data/resources/window.blp
Original file line number Diff line number Diff line change
Expand Up @@ -399,7 +399,7 @@ template $DialectWindow : Adw.ApplicationWindow {
Button clear_btn {
action-name: "win.clear";
tooltip-text: _("Clear");
icon-name: "edit-clear-all-symbolic";
icon-name: "edit-clear-symbolic";
}

Button paste_btn {
Expand Down
6 changes: 3 additions & 3 deletions dialect/dialect.in
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@
# Copyright 2020 Rafael Mardojai CM
# SPDX-License-Identifier: GPL-3.0-or-later

import gettext
import locale
import os
import sys
import signal
import locale
import gettext
import sys

pkgdatadir = '@pkgdatadir@'
localedir = '@localedir@'
Expand Down
2 changes: 2 additions & 0 deletions dialect/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,13 @@
import sys

import gi

try:
gi.require_version('Gdk', '4.0')
gi.require_version('Gtk', '4.0')
gi.require_version('Gst', '1.0')
gi.require_version('Adw', '1')
gi.require_version('Secret', '1')
gi.require_version('Soup', '3.0')

from gi.repository import Adw, Gio, GLib, Gst, Gtk
Expand Down
1 change: 1 addition & 0 deletions dialect/meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ sources = [
'session.py',
'settings.py',
'shortcuts.py',
'utils.py',
'window.py',
]
# Install sources
Expand Down
2 changes: 1 addition & 1 deletion dialect/preferences.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@
from gi.repository import Adw, Gio, Gtk

from dialect.define import RES_PATH
from dialect.providers import MODULES, TTS, ProviderFeature, ProvidersListModel
from dialect.settings import Settings
from dialect.providers import ProviderFeature, ProvidersListModel, MODULES, TTS
from dialect.widgets import ProviderPreferences


Expand Down
7 changes: 6 additions & 1 deletion dialect/providers/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,13 @@

from gi.repository import Gio, GObject

from dialect.providers.base import ProviderCapability, ProviderFeature, ProviderError, ProviderErrorCode # noqa
from dialect.providers import modules
from dialect.providers.base import ( # noqa
ProviderCapability,
ProviderError,
ProviderErrorCode,
ProviderFeature,
)

MODULES = {}
TRANSLATORS = {}
Expand Down
139 changes: 93 additions & 46 deletions dialect/providers/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,9 @@
from enum import Enum, Flag, auto
from typing import Callable, Optional

from gi.repository import Gio

from dialect.define import APP_ID, LANG_ALIASES
from dialect.define import LANG_ALIASES
from dialect.languages import get_lang_name
from dialect.providers.settings import ProviderSettings


class ProviderCapability(Flag):
Expand All @@ -32,6 +31,8 @@ class ProviderFeature(Flag):
""" If the api key is supported but not necessary """
API_KEY_REQUIRED = auto()
""" If the api key is required for the provider to work """
API_KEY_USAGE = auto()
""" If the service reports api usage """
DETECTION = auto()
""" If it supports detecting text language (Auto translation) """
MISTAKES = auto()
Expand All @@ -42,6 +43,19 @@ class ProviderFeature(Flag):
""" If it supports sending translation suggestions to the service """


class ProvideLangModel(Enum):
STATIC = auto()
"""
The provider populate its `src_languages` and `dest_languages` properties.
The `cmp_langs` method will be used to decide if one code can be translated to another.
"""
DYNAMIC = auto()
"""
The provider only populate its `src_languages` property.
The `dest_langs_for` method will be used to get possible destination codes for a code.
"""


class ProviderErrorCode(Enum):
UNEXPECTED = auto()
NETWORK = auto()
Expand Down Expand Up @@ -82,6 +96,8 @@ class BaseProvider:
""" Provider capabilities, translation, tts, etc """
features: ProviderFeature = ProviderFeature.NONE
""" Provider features """
lang_model: ProvideLangModel = ProvideLangModel.STATIC
""" Translation language model """

defaults = {
'instance_url': '',
Expand All @@ -92,8 +108,10 @@ class BaseProvider:
""" Default provider settings """

def __init__(self):
self.languages = []
""" Languages available for translating """
self.src_languages = []
""" Source languages available for translating """
self.dest_languages = []
""" Destination languages available for translating """
self.tts_languages = []
""" Languages available for TTS """
self._nonstandard_langs = {}
Expand All @@ -108,14 +126,13 @@ def __init__(self):
""" Here we save the translation history """

# GSettings
self.settings = Gio.Settings(f'{APP_ID}.translator', f'/app/drey/Dialect/translators/{self.name}/')
self.settings = ProviderSettings(self.name, self.defaults)

"""
Providers API methods
"""

@staticmethod
def validate_instance(url: str, on_done: Callable[[bool], None], on_fail: Callable[[ProviderError], None]):
def validate_instance(self, url: str, on_done: Callable[[bool], None], on_fail: Callable[[ProviderError], None]):
"""
Validate an instance of the provider.
Expand Down Expand Up @@ -217,6 +234,41 @@ def speech(
"""
raise NotImplementedError()

def api_char_usage(
self,
on_done: Callable[[int, int], None],
on_fail: Callable[[ProviderError], None],
):
"""
Retrieves the API usage status
Args:
on_done: Called after the process successful, with the usage and limit as args
on_fail: Called after any error on the speech process
"""
raise NotImplementedError()

def cmp_langs(self, a: str, b: str) -> bool:
"""
Compare two language codes.
It assumes that the codes have been normalized by `normalize_lang_code`.
This method exists so providers can add additional comparison logic.
Args:
a: First lang to compare
b: Second lang to compare
"""

return a == b

def dest_langs_for(self, code: str) -> list[str]:
"""
Get the available destination languages for a source language.
"""
raise NotImplementedError()

@property
def lang_aliases(self) -> dict[str, str]:
"""
Expand All @@ -241,11 +293,11 @@ def lang_aliases(self) -> dict[str, str]:
@property
def instance_url(self):
"""Instance url saved on settings."""
return self.settings.get_string('instance-url') or self.defaults['instance_url']
return self.settings.instance_url or self.defaults['instance_url']

@instance_url.setter
def instance_url(self, url):
self.settings.set_string('instance-url', url)
self.settings.instance_url = url

def reset_instance_url(self):
"""Resets saved instance url."""
Expand All @@ -254,41 +306,41 @@ def reset_instance_url(self):
@property
def api_key(self):
"""API key saved on settings."""
return self.settings.get_string('api-key') or self.defaults['api_key']
return self.settings.api_key or self.defaults['api_key']

@api_key.setter
def api_key(self, api_key):
self.settings.set_string('api-key', api_key)
self.settings.api_key = api_key

def reset_api_key(self):
"""Resets saved API key."""
self.api_key = ''

@property
def src_langs(self):
def recent_src_langs(self):
"""Saved recent source langs of the user."""
return self.settings.get_strv('src-langs') or self.defaults['src_langs']
return self.settings.src_langs or self.defaults['src_langs']

@src_langs.setter
def src_langs(self, src_langs):
self.settings.set_strv('src-langs', src_langs)
@recent_src_langs.setter
def recent_src_langs(self, src_langs):
self.settings.src_langs = src_langs

def reset_src_langs(self):
"""Reset saved recent user source langs"""
self.src_langs = []
self.recent_src_langs = []

@property
def dest_langs(self):
def recent_dest_langs(self):
"""Saved recent destination langs of the user."""
return self.settings.get_strv('dest-langs') or self.defaults['dest_langs']
return self.settings.dest_langs or self.defaults['dest_langs']

@dest_langs.setter
def dest_langs(self, dest_langs):
self.settings.set_strv('dest-langs', dest_langs)
@recent_dest_langs.setter
def recent_dest_langs(self, dest_langs):
self.settings.dest_langs = dest_langs

def reset_dest_langs(self):
"""Reset saved recent user destination langs"""
self.dest_langs = []
self.recent_dest_langs = []

"""
General provider helpers
Expand All @@ -301,10 +353,11 @@ def format_url(url: str, path: str = '', params: dict = {}, http: bool = False):
If url is localhost, `http` is ignored and HTTP protocol is forced.
url: Base url, hostname and tld
path: Path of the url
params: Params to populate a url query
http: If HTTP should be used instead of HTTPS
Args:
url: Base url, hostname and tld
path: Path of the url
params: Params to populate a url query
http: If HTTP should be used instead of HTTPS
"""

if not path.startswith('/'):
Expand Down Expand Up @@ -354,22 +407,14 @@ def normalize_lang_code(self, code: str):

return code

def cmp_langs(self, a: str, b: str) -> bool:
"""
Compare two language codes.
It assumes that the codes have been normalized by `normalize_lang_code`.
This method exists so providers can add additional comparison logic.
Args:
a: First lang to compare
b: Second lang to compare
"""

return a == b

def add_lang(self, original_code: str, name: str = None, trans: bool = True, tts: bool = False):
def add_lang(
self,
original_code: str,
name: str = None,
trans_src: bool = True,
trans_dest: bool = True,
tts: bool = False,
):
"""
Add lang supported by provider after normalization.
Expand All @@ -384,8 +429,10 @@ def add_lang(self, original_code: str, name: str = None, trans: bool = True, tts

code = self.normalize_lang_code(original_code) # Get normalized lang code

if trans: # Add lang to supported languages list
self.languages.append(code)
if trans_src: # Add lang to supported languages list
self.src_languages.append(code)
if trans_dest:
self.dest_languages.append(code)
if tts: # Add lang to supported TTS languages list
self.tts_languages.append(code)

Expand Down
5 changes: 2 additions & 3 deletions dialect/providers/modules/bing.py
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ def on_response(data):
message = self.create_message('GET', self.html_url, headers=self._headers)

# Do async request
self.send_and_read_and_process_response(message, on_response, on_fail, json=False)
self.send_and_read_and_process_response(message, on_response, on_fail, False, False)

def translate(self, text, src, dest, on_done, on_fail):
def on_response(data):
Expand Down Expand Up @@ -145,8 +145,7 @@ def on_response(data):
# Do async request
self.send_and_read_and_process_response(message, on_response, on_fail)

@staticmethod
def check_known_errors(data):
def check_known_errors(self, _status, data):
if not data:
return ProviderError(ProviderErrorCode.EMPTY, 'Response is empty!')

Expand Down
Loading

0 comments on commit b490323

Please sign in to comment.