Repository navigation
Implement localization on xblocks-core, xblocks-extra #241
Description
Summary
Define and document the translation architecture for consolidated XBlock repositories:
- xblocks-contrib
- xblocks-extra
Relevant References
-
openedx-translations:
https://github.com/openedx/openedx-translations/tree/main/translations -
openedx-atlas (CLI to pull translations):
https://github.com/openedx/openedx-atlasGlob pattern support docs:
https://github.com/openedx/openedx-atlas/blob/1eb61a69e8a0f9c0911fc5d334066d3a06b3d425/docs/decisions/0001-support-glob-pattern.rst#L70 -
Archived PR:
chore: add xblocks-contrib to translation extraction pipeline openedx-translations#70340 -
openedx-platform management command:
https://github.com/openedx/openedx-platform/blob/master/common/djangoapps/xblock_django/management/commands/pull_xblock_translations.pyUsage:
make pull_xblock_translations(run inside lms-shell)python manage.py lms pull_xblock_translations --filter=ar,fr_CA
Proposed Strategy
- No shared root-level
confdirectory in consolidated repositories (xblocks-contrib/xblocks-extra) as per current implementation - Each XBlock maintains its own
confdirectory within its module_name - Each XBlock has a separate entry in
openedx-translations/transifex.yml - Update
make extract_translationsinxblocks-*repos to extract translations- It will extract per block
- The daily cron job in
openedx-translationsshould: - Checkoutxblocks-extra/xblocks-contrib- Extract translation source files for each XBlock independently
- Ensure translation path resolution remains compatible with
openedx-platform
Path Compatibility Validation
Current lookup pattern in openedx-platform:
translations/*/{module_name}/conf/locale:{module_name}
Proposed path of xblock translations for consolidated (xblocks-*) repositories:
openedx-translations/translations/xblocks-*/MODULE_NAME/conf/locale/en/LC_MESSAGES/django.po
Example path:
openedx-translations/translations/xblocks-contrib/xblock_pdf/conf/locale/en/LC_MESSAGES/django.po
This path is compatible for openedx-platform lookup pattern.
Example of existing path of Audio xblock:
openedx-translations/translations/AudioXBlock/audio/conf/locale
Cleanup / Migration Tasks
- Verify whether legacy translation paths are auto-removed
- If not, remove obsolete translation directories explicitly
- Validate full extraction + pull flow after migration
Definition of Done
- Architecture finalized for consolidated XBlock repos
- Extraction workflow updated
-
transifex.ymlupdated with per-XBlock entries - Compatibility with
openedx-platformverified - Legacy paths cleaned up
- End-to-end translation flow validated
- Translation works after the deletion of older repositories translations or builtin blocks code
Metadata
Metadata
Assignees
Labels
Type
Projects
- StatusShow more project fields🔖 Ready
Activity
farhan commentedon Apr 28, 2026
FYI: @brian-smith-tcril , @feanil , @openedx/axim-aximprovements
kdmccormick commentedon May 6, 2026
I think that xblock translations are currently broken for Verawood, and that we should prioritize backporting a fix to them.
Copying my comment in from:
Based on my research it seems that, yes, the repo name is always present in openedx-translations path. But when openedx-platform does an atlas pull , it ignores the repo name. So, as long as the python import paths stay constant, the name of the repo can change.
This means:
xblocks_contrib.problem->xblock_problem) do affect translations, so we should land that change before running translation extraction.farhan commentedon May 6, 2026
Agreed with all points,
I didn't get it, can you please elaborate. Here are the testing notes
kdmccormick commentedon May 6, 2026
OK, I was half-mistaken here.
It is true that the import path is part of the identify ofa translatable string. So, when we change the import path of blocks, we have a whole new catalog of translatable strings.
However, there is also a fallback mechanism: when the translation is missing from a catalog, Django will fall back to other catalogs with strings with that also match. The extracted blocks are working because their translations are missing, and they are falling back to the built-in block translations in openedx-platform.
Critically, this means that we cannot remove the built-in blocks until we have translations for the extracted blocks.
Here's the full details from Claude; it explains better than I could:
My prompt:
Claude's Answer:
Great observation, and your intuition is half right — let me show you exactly what's happening.
Quick read on the actual XBlockI18nService.init to nail down the fallback mechanism.
Read the docstring of
XBlockI18nService.__init__— it literally has a name for what you're observing:— xmodule/modulestore/django.py:399-400
What's actually happening
You had it almost right, but with one nuance: gettext catalogs are keyed by msgid alone, but the module name still controls which catalog gets opened. The trick is the fallback chain.
Trace through
__init__for an xblocks-core block, say a problem block whose root module isxblocks_core.problem:get_python_locale(block)returns('django', conf/plugins-locale/xblock.v1/xblocks_core/)if that dir exists, otherwise the bundled-package fallback path.gettext.translation('django', '<that path>', ['es_419']).OSError(no catalog at that path), and theexceptsilently swallows it.self.translatorstays as the default set on line 402:django.utils.translation.__getattr__dispatchesservice.gettext("Grading method:")→django.utils.translation.gettext("Grading method:").LOCALE_PATHS, which points atconf/locale/. Thatdjango.mowas populated bymake pull_translationsfromtranslations/edx-platform/conf/locale/, which already contains "Grading method:" → "Método de calificación:" because the original xmodule code (still living in this repo) had that string and it got extracted into the main edx-platform catalog.Why this works for xblocks-core specifically
Because xblocks-core's strings are the same strings as xmodule's (you forked them out). The main edx-platform catalog has English msgids that happen to match the English msgids in xblocks-core verbatim, and gettext doesn't care where they came from — it just hashes the source string.
Why this is fragile
The "by coincidence" name is well-chosen. This breaks down in three predictable ways:
.po. Next translation cycle, "Grading method:" exits the edx-platform catalog. The xblocks-core blocks then need their own atlas-pulled catalog (or the bundled-fallback path) to be in place, or translations vanish.So the current behavior is real translation, but it's load-bearing on the fact that you haven't yet removed the xmodule originals — and on every msgid being byte-identical between the two codebases.
farhan commentedon May 7, 2026
@kdmccormick Thanks for digging down and sharing your communication with the agent.
That really helped in understanding the fallback mechanism of edx-platform/django and reason of working xblocks-core translations.
How did agent test the translations it-self on the browser, any additional access or just better prompt?
kdmccormick commentedon May 7, 2026
It didn't test them in the browser, it just read the code. I confirmed that es419 was working in the browser myself before sending that prompt.
In the same session, I had previously built up its understanding of translations by sending this prompt:
farhan commentedon Jun 22, 2026
epic blocking reasons are mentioned in this sub-ticket: #242 (comment)
[-]Implement localization on xblocks-contrib, xblocks-extra[/-][+]Implement localization on xblocks-core, xblocks-extra[/+]