-
Notifications
You must be signed in to change notification settings - Fork 6
feat: Add ol_openedx_ai_static_translations plugin; clean up ol_openedx_course_translations #769
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 5 commits
ccd2b72
feae6ed
fed1f3b
877eccb
94b32bc
020fd79
7226134
f71e486
6f187b3
a823902
3359403
b05ac9d
9a128d5
8c540dd
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| Change Log | ||
| ---------- | ||
|
|
||
| .. | ||
| All enhancements and patches to ol_openedx_ai_static_translations will be documented | ||
| in this file. It adheres to the structure of https://keepachangelog.com/ , | ||
| but in reStructuredText instead of Markdown (for ease of incorporation into | ||
| Sphinx documentation and the PyPI description). | ||
| This project adheres to Semantic Versioning (https://semver.org/). | ||
| .. There should always be an "Unreleased" section for changes pending release. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,28 @@ | ||
| Copyright (C) 2022 MIT Open Learning | ||
|
|
||
| All rights reserved. | ||
|
|
||
| Redistribution and use in source and binary forms, with or without | ||
| modification, are permitted provided that the following conditions are met: | ||
|
|
||
| * Redistributions of source code must retain the above copyright notice, this | ||
| list of conditions and the following disclaimer. | ||
|
|
||
| * Redistributions in binary form must reproduce the above copyright notice, | ||
| this list of conditions and the following disclaimer in the documentation | ||
| and/or other materials provided with the distribution. | ||
|
|
||
| * Neither the name of the copyright holder nor the names of its | ||
| contributors may be used to endorse or promote products derived from | ||
| this software without specific prior written permission. | ||
|
|
||
| THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | ||
| AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | ||
| IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | ||
| DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE | ||
| FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL | ||
| DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR | ||
| SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER | ||
| CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, | ||
| OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | ||
| OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,54 @@ | ||
| OL Open edX AI Static Translations | ||
| ==================================== | ||
|
|
||
| An Open edX plugin that provides AI-powered static translation management. It syncs translation keys, translates them using LLM providers, and creates pull requests with translated content. | ||
|
|
||
| Purpose | ||
| ******* | ||
|
|
||
| This plugin provides the ``sync_and_translate_language`` management command for syncing and translating Open edX static strings (frontend JSON and backend PO files) using LLM providers (OpenAI, Gemini, Mistral) with optional glossary support. | ||
|
|
||
| Setup | ||
| ===== | ||
|
|
||
| For detailed installation instructions, please refer to the `plugin installation guide <../../docs#installation-guide>`_. | ||
|
|
||
| Installation required in: | ||
|
|
||
| * Studio (CMS) | ||
|
|
||
| Configuration | ||
| ============= | ||
|
|
||
| This plugin shares settings with ``ol_openedx_course_translations``. Ensure the following settings are configured: | ||
|
|
||
| .. code-block:: python | ||
|
|
||
| TRANSLATIONS_PROVIDERS: { | ||
| "default_provider": "mistral", | ||
| "openai": {"api_key": "", "default_model": "gpt-5.2"}, | ||
| "gemini": {"api_key": "", "default_model": "gemini-3-pro-preview"}, | ||
| "mistral": {"api_key": "", "default_model": "mistral-large-latest"}, | ||
| } | ||
| TRANSLATIONS_GITHUB_TOKEN: <YOUR_GITHUB_TOKEN> | ||
| TRANSLATIONS_REPO_PATH: "" | ||
| TRANSLATIONS_REPO_URL: "https://github.com/mitodl/mitxonline-translations.git" | ||
|
|
||
| Usage | ||
| ===== | ||
|
|
||
| .. code-block:: bash | ||
|
|
||
| # Sync and translate a language | ||
| ./manage.py cms sync_and_translate_language el | ||
|
|
||
| # With specific provider and model | ||
| ./manage.py cms sync_and_translate_language el --provider openai --model gpt-5.2 --glossary /path/to/glossary | ||
|
|
||
| License | ||
| ******* | ||
|
|
||
| The code in this repository is licensed under the BSD 3-Clause license unless | ||
| otherwise noted. | ||
|
|
||
| Please see `LICENSE.txt <LICENSE.txt>`_ for details. | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| """ | ||
| MIT's Open edX AI static translations plugin | ||
| """ |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,27 @@ | ||
| """ | ||
| ol_openedx_ai_static_translations Django application initialization. | ||
| """ | ||
|
|
||
| from django.apps import AppConfig | ||
| from edx_django_utils.plugins import PluginSettings | ||
| from openedx.core.djangoapps.plugins.constants import ProjectType, SettingsType | ||
|
|
||
|
|
||
| class OLOpenedXAIStaticTranslationsConfig(AppConfig): | ||
| """ | ||
| Configuration for the ol_openedx_ai_static_translations Django application. | ||
| """ | ||
|
|
||
| name = "ol_openedx_ai_static_translations" | ||
| verbose_name = "OL AI Static Translations" | ||
|
|
||
| plugin_app = { | ||
| PluginSettings.CONFIG: { | ||
| ProjectType.CMS: { | ||
| SettingsType.COMMON: {PluginSettings.RELATIVE_PATH: "settings.cms"}, | ||
| }, | ||
| ProjectType.LMS: { | ||
| SettingsType.COMMON: {PluginSettings.RELATIVE_PATH: "settings.lms"}, | ||
| }, | ||
| }, | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,235 @@ | ||
| """Constants for AI static translation synchronization.""" | ||
|
|
||
| # LLM Provider names | ||
| PROVIDER_DEEPL = "deepl" | ||
| PROVIDER_GEMINI = "gemini" | ||
| PROVIDER_MISTRAL = "mistral" | ||
| PROVIDER_OPENAI = "openai" | ||
|
|
||
| # Learner-facing frontend applications that require translation | ||
| LEARNER_FACING_APPS = [ | ||
| "frontend-app-learning", | ||
| "frontend-app-learner-dashboard", | ||
| "frontend-app-learner-record", | ||
| "frontend-app-account", | ||
| "frontend-app-profile", | ||
| "frontend-app-authn", | ||
| "frontend-app-catalog", | ||
| "frontend-app-discussions", | ||
| "frontend-component-header", | ||
| "frontend-component-footer", | ||
| "frontend-app-ora", | ||
| "frontend-platform", | ||
| ] | ||
|
|
||
| # Plural forms configuration for different languages | ||
| # Based on GNU gettext plural forms specification | ||
| # See: https://www.gnu.org/software/gettext/manual/html_node/Plural-forms.html | ||
| PLURAL_FORMS = { | ||
| # Languages with no plural forms (nplurals=1) | ||
| "ja": "nplurals=1; plural=0;", # Japanese | ||
| "ko": "nplurals=1; plural=0;", # Korean | ||
| "zh": "nplurals=1; plural=0;", # Chinese (all variants) | ||
| "th": "nplurals=1; plural=0;", # Thai | ||
| "vi": "nplurals=1; plural=0;", # Vietnamese | ||
| "id": "nplurals=1; plural=0;", # Indonesian | ||
| "ms": "nplurals=1; plural=0;", # Malay | ||
| "km": "nplurals=1; plural=0;", # Khmer | ||
| "bo": "nplurals=1; plural=0;", # Tibetan | ||
| # Languages with 2 plural forms: plural=(n != 1) | ||
| "en": "nplurals=2; plural=(n != 1);", # English | ||
| "es": "nplurals=2; plural=(n != 1);", # Spanish (all variants) | ||
| "de": "nplurals=2; plural=(n != 1);", # German | ||
| "el": "nplurals=2; plural=(n != 1);", # Greek | ||
| "it": "nplurals=2; plural=(n != 1);", # Italian | ||
| "pt": "nplurals=2; plural=(n != 1);", # Portuguese (all variants) | ||
| "nl": "nplurals=2; plural=(n != 1);", # Dutch | ||
| "sv": "nplurals=2; plural=(n != 1);", # Swedish | ||
| "da": "nplurals=2; plural=(n != 1);", # Danish | ||
| "no": "nplurals=2; plural=(n != 1);", # Norwegian | ||
| "nb": "nplurals=2; plural=(n != 1);", # Norwegian Bokmål | ||
| "nn": "nplurals=2; plural=(n != 1);", # Norwegian Nynorsk | ||
| "fi": "nplurals=2; plural=(n != 1);", # Finnish | ||
| "is": "nplurals=2; plural=(n != 1);", # Icelandic | ||
| "et": "nplurals=2; plural=(n != 1);", # Estonian | ||
| "lv": "nplurals=2; plural=(n != 1);", # Latvian | ||
| "he": "nplurals=2; plural=(n != 1);", # Hebrew | ||
| "hi": "nplurals=2; plural=(n != 1);", # Hindi | ||
| "bn": "nplurals=2; plural=(n != 1);", # Bengali | ||
| "gu": "nplurals=2; plural=(n != 1);", # Gujarati | ||
| "kn": "nplurals=2; plural=(n != 1);", # Kannada | ||
| "ml": "nplurals=2; plural=(n != 1);", # Malayalam | ||
| "ta": "nplurals=2; plural=(n != 1);", # Tamil | ||
| "te": "nplurals=2; plural=(n != 1);", # Telugu | ||
| "or": "nplurals=2; plural=(n != 1);", # Oriya | ||
| "si": "nplurals=2; plural=(n != 1);", # Sinhala | ||
| "ne": "nplurals=2; plural=(n != 1);", # Nepali | ||
| "mr": "nplurals=2; plural=(n != 1);", # Marathi | ||
| "ur": "nplurals=2; plural=(n != 1);", # Urdu | ||
| "az": "nplurals=2; plural=(n != 1);", # Azerbaijani | ||
| "uz": "nplurals=2; plural=(n != 1);", # Uzbek | ||
| "kk": "nplurals=2; plural=(n != 1);", # Kazakh | ||
| "mn": "nplurals=2; plural=(n != 1);", # Mongolian | ||
| "sq": "nplurals=2; plural=(n != 1);", # Albanian | ||
| "eu": "nplurals=2; plural=(n != 1);", # Basque | ||
| "ca": "nplurals=2; plural=(n != 1);", # Catalan | ||
| "gl": "nplurals=2; plural=(n != 1);", # Galician | ||
| "tr": "nplurals=2; plural=(n != 1);", # Turkish | ||
| "af": "nplurals=2; plural=(n != 1);", # Afrikaans | ||
| "fil": "nplurals=2; plural=(n != 1);", # Filipino | ||
| # Languages with 2 plural forms: plural=(n > 1) | ||
| "fr": "nplurals=2; plural=(n > 1);", # French | ||
| "br": "nplurals=2; plural=(n > 1);", # Breton | ||
| # Languages with 3 plural forms | ||
| "pl": ( | ||
| "nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && " | ||
| "(n%100<10 || n%100>=20) ? 1 : 2);" | ||
| ), # Polish | ||
| "ru": ( | ||
| "nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && " | ||
| "(n%100<10 || n%100>=20) ? 1 : 2);" | ||
| ), # Russian | ||
| "uk": ( | ||
| "nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && " | ||
| "(n%100<10 || n%100>=20) ? 1 : 2);" | ||
| ), # Ukrainian | ||
| "be": ( | ||
| "nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && " | ||
| "(n%100<10 || n%100>=20) ? 1 : 2);" | ||
| ), # Belarusian | ||
| "sr": ( | ||
| "nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && " | ||
| "(n%100<10 || n%100>=20) ? 1 : 2);" | ||
| ), # Serbian | ||
| "hr": ( | ||
| "nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && " | ||
| "(n%100<10 || n%100>=20) ? 1 : 2);" | ||
| ), # Croatian | ||
| "bs": ( | ||
| "nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && " | ||
| "(n%100<10 || n%100>=20) ? 1 : 2);" | ||
| ), # Bosnian | ||
| "cs": "nplurals=3; plural=(n==1 ? 0 : (n>=2 && n<=4) ? 1 : 2);", # Czech | ||
| "sk": "nplurals=3; plural=(n==1 ? 0 : (n>=2 && n<=4) ? 1 : 2);", # Slovak | ||
| "lt": ( | ||
| "nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && " | ||
| "(n%100<10 || n%100>=20) ? 1 : 2);" | ||
| ), # Lithuanian | ||
| "hy": "nplurals=3; plural=(n==1 ? 0 : n>=2 && n<=4 ? 1 : 2);", # Armenian | ||
| "ro": ( | ||
| "nplurals=3; plural=(n==1 ? 0 : (n==0 || (n%100 > 0 && n%100 < 20)) ? 1 : 2);" | ||
| ), # Romanian | ||
| # Languages with 4 plural forms | ||
| "cy": ( | ||
| "nplurals=4; plural=(n==1 ? 0 : n==2 ? 1 : (n==8 || n==11) ? 2 : 3);" | ||
| ), # Welsh | ||
| "ga": "nplurals=4; plural=(n==1 ? 0 : n==2 ? 1 : (n>2 && n<7) ? 2 : 3);", # Irish | ||
| "gd": ( | ||
| "nplurals=4; plural=(n==1 || n==11) ? 0 : (n==2 || n==12) ? 1 : " | ||
| "(n>2 && n<20) ? 2 : 3);" | ||
| ), # Scottish Gaelic | ||
| "mt": ( | ||
| "nplurals=4; plural=(n==1 ? 0 : n==0 || (n%100>=2 && n%100<=10) ? 1 : " | ||
| "(n%100>=11 && n%100<=19) ? 2 : 3);" | ||
| ), # Maltese | ||
| # Languages with 6 plural forms | ||
| "ar": ( | ||
| "nplurals=6; plural=(n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 && " | ||
| "n%100<=10 ? 3 : n%100>=11 && n%100<=99 ? 4 : 5);" | ||
| ), # Arabic | ||
| # Other languages | ||
| "fa": "nplurals=2; plural=(n==0 || n==1 ? 0 : 1);", # Persian/Farsi | ||
| "hu": "nplurals=2; plural=(n != 1);", # Hungarian | ||
| "bg": "nplurals=2; plural=(n != 1);", # Bulgarian | ||
| "am": "nplurals=2; plural=(n > 1);", # Amharic | ||
| } | ||
|
|
||
| # Default plural form fallback (English-style) | ||
| # Used when a language code is not found in PLURAL_FORMS | ||
| DEFAULT_PLURAL_FORM = "nplurals=2; plural=(n != 1);" | ||
|
|
||
| # Typo patterns to fix in translation files | ||
| TYPO_PATTERNS = [ | ||
| ("Serch", "Search"), | ||
| ] | ||
|
|
||
| # Backend PO file names | ||
| BACKEND_PO_FILES = ["django.po", "djangojs.po"] | ||
|
|
||
| # Backend plugin apps: (repo_dir, module_name) under translations/. | ||
| # Used by sync_and_translate_language to sync/translate at | ||
| # translations/<repo_dir>/<module_name>/conf/locale/<lang>/LC_MESSAGES/django.po. | ||
| # When pulled in edx-platform (make pull_translations), these go to | ||
| # conf/plugins-locale/plugins/<module_name>/. | ||
| TRANSLATABLE_PLUGINS = [ | ||
| ("open-edx-plugins", "ol_openedx_chat"), | ||
| ] | ||
|
|
||
| # PO file header metadata | ||
| PO_HEADER_PROJECT_VERSION = "0.1a" | ||
| PO_HEADER_BUGS_EMAIL = "openedx-translation@googlegroups.com" | ||
| PO_HEADER_POT_CREATION_DATE = "2023-06-13 08:00+0000" | ||
| PO_HEADER_MIME_VERSION = "1.0" | ||
| PO_HEADER_CONTENT_TYPE = "text/plain; charset=UTF-8" | ||
| PO_HEADER_CONTENT_TRANSFER_ENCODING = "8bit" | ||
| PO_HEADER_TRANSIFEX_TEAM_BASE_URL = "https://app.transifex.com/open-edx/teams/6205" | ||
|
|
||
| # File and directory names | ||
| TRANSLATION_FILE_NAMES = { | ||
| "transifex_input": "transifex_input.json", | ||
| "english": "en.json", | ||
| "messages_dir": "messages", | ||
| "i18n_dir": "i18n", | ||
| "locale_dir": "locale", | ||
| "lc_messages": "LC_MESSAGES", | ||
| "conf_dir": "conf", | ||
| "edx_platform": "edx-platform", | ||
| } | ||
|
|
||
| # JSON file formatting | ||
| DEFAULT_JSON_INDENT = 2 | ||
|
|
||
| # Language code to human-readable name mapping | ||
| # Used in PO file headers for Language-Team field | ||
| LANGUAGE_MAPPING = { | ||
| "ar": "Arabic", | ||
| "de": "German", | ||
| "el": "Greek", | ||
| "es": "Spanish", | ||
| "fr": "French", | ||
| "hi": "Hindi", | ||
| "id": "Indonesian", | ||
| "ja": "Japanese", | ||
| "kr": "Korean", | ||
| "pt": "Portuguese", | ||
| "ru": "Russian", | ||
| "sq": "Albanian", | ||
| "tr": "Turkish", | ||
| "zh": "Chinese", | ||
| } | ||
|
|
||
| # Maximum number of retries for failed translation batches | ||
| MAX_RETRIES = 3 | ||
|
|
||
| # Glossary parsing constants | ||
| EXPECTED_GLOSSARY_PARTS = 2 # English term and translation separated by "->" | ||
|
|
||
| # HTTP Status Codes | ||
| HTTP_OK = 200 | ||
| HTTP_CREATED = 201 | ||
| HTTP_NOT_FOUND = 404 | ||
| HTTP_TOO_MANY_REQUESTS = 429 | ||
| HTTP_UNPROCESSABLE_ENTITY = 422 | ||
|
|
||
| # Error message length limit | ||
| MAX_ERROR_MESSAGE_LENGTH = 200 | ||
|
|
||
| # Maximum length for strings in log messages (truncate with "...") | ||
| MAX_LOG_STRING_LENGTH = 50 | ||
| MAX_LOG_ICU_STRING_LENGTH = 100 | ||
|
|
||
| # Plural category counts (GNU gettext nplurals) | ||
| PLURAL_CATEGORIES_ARABIC = 6 # zero, one, two, few, many, other | ||
| PLURAL_CATEGORIES_FOUR = 4 # one, two, few, other | ||
| PLURAL_CATEGORIES_THREE = 3 # one, few, other | ||
| PLURAL_CATEGORIES_TWO = 2 # one, other (most languages) | ||
|
Comment on lines
+225
to
+235
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Bug: The Suggested FixIn Prompt for AI Agent |
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These two settings are confusing, there should be a documentation on which setting does what.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added inline documentation in 020fd79:
TRANSLATIONS_GITHUB_TOKEN— personal access token with repo write permissions for creating PRsTRANSLATIONS_REPO_PATH— local filesystem path where the translations repo will be cloned/checked outTRANSLATIONS_REPO_URL— URL of the remote translations repository