How to customize i18n (translation)

I did fork the openedx-translations repository according to the tutor documentation, created a branch “teak.2-custom” on my forked repository and made my desired changes on the existing English verbiage, commit and pushed to my branch “teak.2-custom”.

Update the atlas revision and repository in the tutor config:

tutor config save \
–set ATLAS_REVISION=“teak.2-custom” \
–set ATLAS_REPOSITORY=“MyUsername/openedx-translations”

Run these commands without error:
tutor images build mfe
tutor images build openedx
tutor local launch

I can confirm during the build for both the mfe & openedx, that it actual pulled from MyUsername/openedx-translations. However, the UI does not reflect my changes.

Please guys can you help me confirm if there is something am doing wrong?

Below if part of the log during mfe build

[+] Building 41.2s (108/149) docker:default
=> [authn-common 6/7] RUN make OPENEDX_ATLAS_PULL=true ATLAS_OPTIONS=“–repository=MyUsername/openedx-translations --revision=teak.2-custom " pull_translations 19.4s
[+] Building 41.4s (108/149) docker:default
=> [authn-common 6/7] RUN make OPENEDX_ATLAS_PULL=true ATLAS_OPTIONS=”–repository=MyUsername/openedx-translations --revision=teak.2-custom " pull_translations 19.6s
[+] Building 650.3s (128/149) docker:default
[+] Building 1738.0s (150/150) FINISHED

Below is part of the log during openedx build:

=> CACHED [production 18/37] RUN make clean_translations 0.0s
=> [production 19/37] RUN ./manage.py lms --settings=tutor.i18n pull_plugin_translations --verbose --repository=‘MyUsername/openedx-translations’ --revision=‘teak.2-custom’ 41.8s
=> [production 20/37] RUN ./manage.py lms --settings=tutor.i18n pull_xblock_translations --repository=‘MyUsername/openedx-translations’ --revision=‘teak.2-custom’ 25.3s
=> [production 21/37] RUN atlas pull --repository=‘MyUsername/openedx-translations’ --revision=‘teak.2-custom’ translations/edx-platform/conf/locale:conf/locale translations/studio-frontend/src 24.8s
=> [production 22/37] RUN ./manage.py lms --settings=tutor.i18n compile_xblock_translations 31.0s
=> [production 23/37] RUN ./manage.py cms --settings=tutor.i18n compile_xblock_translations 27.0s
=> [production 24/37] RUN ./manage.py lms --settings=tutor.i18n compile_plugin_translations 17.7s
=> [production 25/37] RUN ./manage.py lms --settings=tutor.i18n compilemessages -v1 22.2s
=> [production 26/37] RUN ./manage.py lms --settings=tutor.i18n compilejsi18n 26.5s
=> [production 27/37] RUN ./manage.py cms --settings=tutor.i18n compilejsi18n 16.7s

@omar or @brian.smith any ideas?

Hopefully I can help here!

To clarify: did you use “MyUsername” in MyUsername/openedx-translations or your GitHub username (the one you forked openedx-translations to)?

Also you mentioned changing the English, what file(s) did you change?

I mean my actual username (My-Github-Username).
The actual files that I updated are:

  • translations/edx-platform/conf/locale/en/LC_MESSAGES/djangojs.po
  • translations/frontend-app-authn/src/i18n/messages/de.json
  • translations/frontend-app-authn/src/i18n/messages/de.json
  • translations/frontend-app-authn/src/i18n/messages/id.json
  • translations/frontend-app-authn/src/i18n/transifex_input.json
  • translations/frontend-app-learner-dashboard/src/i18n/transifex_input.json

I noticed the text I need to update are contained in these files, hence the changes (though I am interested in the English version only atleast for now)

That is definitely what is making this more complicated.

Modifying transifex_input.json doesn’t change anything for MFEs. Since English is the default/fallback language, changing the English strings for MFEs requires modifying source files in the MFEs themselves.

Supporting this use case for MFEs would require architectural changes.

Detailed explanation/example

As the name suggests for these files, they are used as input for Transifex. They are just a compilation of the English source strings that exist within each MFE.

Using logistration.sign.in in frontend-app-authn as an example:

The source string exists in src/common-components/messages.jsx:

  'logistration.sign.in': {
    id: 'logistration.sign.in',
    defaultMessage: 'Sign in',
    description: 'Text that appears on the tab to switch between login and register',
  },

And extracted/concatenated into transifex_input.json when make extract_translations is run.

When make pull_translations is run, the src/i18n/messages directory is populated

├── i18n
│   ├── index.js
│   └── messages
│       ├── frontend-app-authn
│       │   ├── af_ZA.json
│       │   ├── ar.json
│       │   ├── az.json
│       │   ├── da.json
│       │   ├── de.json
│       │   ├── de_DE.json
│       │   ├── el.json
│       │   ├── es_419.json
│       │   ├── es_ES.json
│       │   ├── fa.json
│       │   ├── fr_CA.json
│       │   ├── he.json
│       │   ├── hi.json
│       │   ├── hu.json
│       │   ├── id.json
│       │   ├── index.js
│       │   ├── it_IT.json
│       │   ├── ja.json
│       │   ├── lv.json
│       │   ├── pt_BR.json
│       │   ├── pt_PT.json
│       │   ├── ro.json
│       │   ├── ru.json
│       │   ├── sv.json
│       │   ├── sw.json
│       │   ├── te.json
│       │   ├── th.json
│       │   ├── tr_TR.json
│       │   ├── uk.json
│       │   ├── uz.json
│       │   ├── vi.json
│       │   ├── zh_CN.json
│       │   └── zh_HK.json
│       ├── frontend-platform
│       │   ├── ar.json
│       │   ├── az.json
│       │   ├── bo.json
│       │   ├── da.json
│       │   ├── de_DE.json
│       │   ├── el.json
│       │   ├── es_419.json
│       │   ├── es_ES.json
│       │   ├── fa.json
│       │   ├── fr_CA.json
│       │   ├── he.json
│       │   ├── hi.json
│       │   ├── hu.json
│       │   ├── id.json
│       │   ├── index.js
│       │   ├── it_IT.json
│       │   ├── ja.json
│       │   ├── lv.json
│       │   ├── pt_BR.json
│       │   ├── pt_PT.json
│       │   ├── ro.json
│       │   ├── ru.json
│       │   ├── te.json
│       │   ├── th.json
│       │   ├── tr_TR.json
│       │   ├── uk.json
│       │   ├── uz.json
│       │   ├── vi.json
│       │   ├── zh_CN.json
│       │   └── zh_HK.json
│       └── paragon
│           ├── af_ZA.json
│           ├── ar.json
│           ├── az.json
│           ├── da.json
│           ├── de.json
│           ├── de_DE.json
│           ├── el.json
│           ├── es_419.json
│           ├── es_ES.json
│           ├── fa.json
│           ├── fr_CA.json
│           ├── he.json
│           ├── hi.json
│           ├── hu.json
│           ├── id.json
│           ├── index.js
│           ├── it_IT.json
│           ├── ja.json
│           ├── lv.json
│           ├── pt_BR.json
│           ├── pt_PT.json
│           ├── ru.json
│           ├── sv.json
│           ├── sw.json
│           ├── te.json
│           ├── th.json
│           ├── tr_TR.json
│           ├── uk.json
│           ├── uz.json
│           ├── vi.json
│           ├── zh_CN.json
│           └── zh_HK.json

but there is no en.json, and no transifex_input.json. The English strings are coming from the default messages.

Adding en.json also doesn’t work (see frontend-platform’s src/i18n/lib.js)

const supportedLocales = [
  'ar', // Arabic
  // NOTE: 'en' is not included in this list intentionally, since it's the fallback.
  'es-419', // Spanish, Latin American
  'fa', // Farsi
  'fa-ir', // Farsi, Iran
  'fr', // French
  'zh-cn', // Chinese, Simplified
  'ca', // Catalan
  'he', // Hebrew
  'id', // Indonesian
  'ko-kr', // Korean (Korea)
  'pl', // Polish
  'pt-br', // Portuguese (Brazil)
  'ru', // Russian
  'th', // Thai
  'uk', // Ukrainian
  'vi', // Vietnamese
];

So the only way to change English strings in MFEs at the moment is to modify the defaultMessages in the MFEs themselves.

I’d need to verify with @omar on this one. I’d think that modifying en/LC_MESSAGES/djangojs.po would lead to changes to the English strings in there, but I haven’t verified.

@brian.smith Thank you so much for your clarification and detailed explaination on this. So just as you suggested I had to clone the individual MFEs and make the changes needed directly in the source. Based on my understanding on what is going on here, I have now made a custom plugin to override the default MFEs of interest and so far, it is looking good (I am not sure if this is the best way to override MFEs though)

from tutormfe.hooks import MFE_APPS
@MFE_APPS.add()
def _override_authn_mfe(apps):
apps[“authn”] = {
“repository”: “https://github.com/MyUsername/frontend-app-authn.git”,
“port”: 1999,
“version”: “teak.201”
}
apps[“learner-dashboard”] = {
“repository”: “https://github.com/MyUsername/frontend-app-learner-dashboard.git”,
“port”: 1996,
“version”: “teak.202”
}

That’s what I haven’t tried. But feel free to do so @pauldic