Skip to content

Commit

Permalink
refactor: move AI translations to service
Browse files Browse the repository at this point in the history
  • Loading branch information
nsprenkle committed Mar 19, 2024
1 parent 6df9588 commit 7cd15f2
Show file tree
Hide file tree
Showing 4 changed files with 74 additions and 58 deletions.
Empty file.
53 changes: 53 additions & 0 deletions lms/djangoapps/ai_translation/service.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
"""
Services for AI Translations.
"""

from hashlib import sha256
import json

from django.conf import settings
from edx_rest_api_client.client import OAuthAPIClient


class AiTranslationService:
"""
A service which communicates with ai-translations for translation-related tasks
"""

def __init__(self):
self._client = None

@property
def client(self):
"""Client for communicating with ai-translations, singleton creation."""
if not self._client:
self._client = self._init_translations_client()
return self._client

def _init_translations_client(self):
"""Initialize OAuth connection to ai-translations"""
return OAuthAPIClient(
base_url=settings.TRANSLATIONS_SERVICE_EDX_OAUTH2_PROVIDER_URL,
client_id=settings.TRANSLATIONS_SERVICE_EDX_OAUTH2_KEY,
client_secret=settings.TRANSLATIONS_SERVICE_EDX_OAUTH2_SECRET,
)

def translate(self, content, language, block_id):
"""Request translated version of content from translations IDA"""

url = f"{settings.AI_TRANSLATIONS_API_URL}/translate-xblock/"
headers = {
"content-type": "application/json",
"use-jwt-cookie": "true",
}
payload = {
"block_id": str(block_id),
"source_language": "en",
"target_language": language,
"content": content,
"content_hash": sha256(content.encode("utf-8")).hexdigest(),
}

response = self.client.post(url, data=json.dumps(payload), headers=headers)

return response.json().get("translated_content")
2 changes: 2 additions & 0 deletions lms/djangoapps/courseware/block_render.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@
from xmodule.services import EventPublishingService, RebindUserService, SettingsService, TeamsConfigurationService
from common.djangoapps.static_replace.services import ReplaceURLService
from common.djangoapps.static_replace.wrapper import replace_urls_wrapper
from lms.djangoapps.ai_translation.service import AiTranslationService
from lms.djangoapps.courseware.access import get_user_role, has_access
from lms.djangoapps.courseware.entrance_exams import user_can_skip_entrance_exam, user_has_passed_entrance_exam
from lms.djangoapps.courseware.masquerade import (
Expand Down Expand Up @@ -635,6 +636,7 @@ def inner_get_block(block: XBlock) -> XBlock | None:
'call_to_action': CallToActionService(),
'publish': EventPublishingService(user, course_id, track_function),
'enrollments': EnrollmentsService(),
'ai_translation': AiTranslationService(),
}

runtime.get_block_for_descriptor = inner_get_block
Expand Down
77 changes: 19 additions & 58 deletions xmodule/html_block.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,11 @@
import sys
import textwrap
from datetime import datetime
from hashlib import sha256

from django.conf import settings
import requests
from fs.errors import ResourceNotFound
from lxml import etree
from path import Path as path
from edx_rest_api_client.client import OAuthAPIClient
from web_fragments.fragment import Fragment
from xblock.core import XBlock
from xblock.fields import Boolean, List, Scope, String
Expand Down Expand Up @@ -46,6 +43,7 @@
@XBlock.needs("i18n")
@XBlock.needs("mako")
@XBlock.needs("user")
@XBlock.wants("ai_translation")
class HtmlBlockMixin( # lint-amnesty, pylint: disable=abstract-method
XmlMixin, EditingMixin,
XModuleToXBlockMixin, ResourceTemplates, XModuleMixin,
Expand Down Expand Up @@ -93,63 +91,26 @@ def student_view(self, context):
"""
Return a fragment that contains the html for the student view
"""
if (context.get("translate_lang")):
return self.translated_view(context, context.get("translate_lang"))

fragment = Fragment(self.get_html())
add_sass_to_fragment(fragment, 'HtmlBlockDisplay.scss')
add_webpack_js_to_fragment(fragment, 'HtmlBlockDisplay')
shim_xmodule_js(fragment, 'HTMLModule')
return fragment

def translated_view(self, _context, translate_lang):
"""
Translated version of this content
"""
# Translate HTML
translated_html = self.translate(self.data, translate_lang)

# Replace placeholder values
translated_html = self.replace_placeholders(translated_html)
# If a translation is requested and the ai_translation service is available, use translate_view
if (context.get("translate_lang") and self.runtime.service(self, 'ai_translation')):
html = self.get_translated_html(context)
else:
html = self.get_html()

# Convert to fragment and add resources
fragment = Fragment(translated_html)
fragment = Fragment(html)
add_sass_to_fragment(fragment, 'HtmlBlockDisplay.scss')
add_webpack_js_to_fragment(fragment, 'HtmlBlockDisplay')
shim_xmodule_js(fragment, 'HTMLModule')
return fragment

def _get_translations_client(self):
return OAuthAPIClient(
base_url=settings.TRANSLATIONS_SERVICE_EDX_OAUTH2_PROVIDER_URL,
client_id=settings.TRANSLATIONS_SERVICE_EDX_OAUTH2_KEY,
client_secret=settings.TRANSLATIONS_SERVICE_EDX_OAUTH2_SECRET,
)

def translate(self, content, language):
""" Request translated version of content from translations IDA"""

translation_client = self._get_translations_client()
url = f'{settings.AI_TRANSLATIONS_API_URL}/translate-xblock/'
headers = {
'content-type': 'application/json',
'use-jwt-cookie': 'true',
}
payload = {
"block_id": str(self.location),
"source_language": "en",
"target_language": language,
"content": content,
"content_hash": sha256(content.encode('utf-8')).hexdigest(),
}

response = translation_client.post(
url,
data=json.dumps(payload),
headers=headers
)

return response.json().get('translated_content')
def get_translated_html(self, context):
""" Returns translated html required for rendering the block, replacing placeholder values"""
if self.data:
translate_lang = context.get("translate_lang")
translation_service = self.runtime.service(self, 'ai_translation')
translated_html = translation_service.translate(self.data, translate_lang, self.location)
return self.substitute_keywords(translated_html)
return self.data

@XBlock.supports("multi_device")
def public_view(self, context):
Expand All @@ -173,12 +134,12 @@ def student_view_data(self, context=None): # pylint: disable=unused-argument
def get_html(self):
""" Returns html required for rendering the block, replacing placeholder values"""
if self.data:
return self.replace_placeholders(self.data)
return self.substitute_keywords(self.data)
return self.data
def replace_placeholders(self, html_with_placeholders):

def substitute_keywords(self, html_with_placeholders):
"""
The HTML block allows replacing of some placeholder tags with contextual info.
The HTML block allows replacing of some tags with contextual info.
Currently implemented:
%%USER_ID%% - User ID
Expand Down

0 comments on commit 7cd15f2

Please sign in to comment.