Skip to content

SF-3311 Add TranslocoInterceptor to wrap English strings for Arabic UI #3204

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

Open
wants to merge 3 commits into
base: master
Choose a base branch
from

Conversation

josephmyers
Copy link
Collaborator

@josephmyers josephmyers commented May 15, 2025

This change utilizes the TranslocoInterceptor interface to check the strings in our localization files for untranslated values. If an RTL value is supplied (via the UI language), it will wrap any strings that don't contain an RTL character. Currently only Arabic is supported, but you could easily add more. This allows punctuation to appear in the correct place for English sentences when the UI is set to Arabic.

I didn't encounter any places in the app that are using preSaveTranslationKey, but it was required for the interface. The preSaveTranslation method is called once for each localization file that's in use, for a total of two times.


This change is Reviewable

@josephmyers josephmyers added the will require testing PR should not be merged until testers confirm testing is complete label May 15, 2025
Copy link

codecov bot commented May 15, 2025

Codecov Report

All modified and coverable lines are covered by tests ✅

Project coverage is 81.87%. Comparing base (24d1b63) to head (65458dc).

✅ All tests successful. No failed tests found.

Additional details and impacted files
@@            Coverage Diff             @@
##           master    #3204      +/-   ##
==========================================
+ Coverage   81.85%   81.87%   +0.01%     
==========================================
  Files         601      602       +1     
  Lines       34500    34526      +26     
  Branches     5591     5578      -13     
==========================================
+ Hits        28241    28267      +26     
- Misses       5426     5438      +12     
+ Partials      833      821      -12     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@josephmyers josephmyers marked this pull request as ready for review May 15, 2025 05:53
@Nateowami
Copy link
Collaborator

Could you just check if the string provided is identical to the English string (since that's what we always fall back to), rather than trying to detect Arabic? This would make it work for other languages as well, with no need to detect the script.

This change utilizes the TranslocoInterceptor interface to check the strings in our localization files for untranslated values. If an RTL value is supplied (via the UI language), it will wrap any strings that don't contain an RTL character. Currently only Arabic is supported, but you could easily add more. This allows punctuation to appear in the correct place for English sentences when the UI is set to Arabic.

I didn't encounter any places in the app that are using preSaveTranslationKey, but it was required for the interface. The preSaveTranslation method is called once for each localization file that's in use, for a total of two times.
… Arabic

This should allow us to support more RTL languages without further changes.

It requires the TranslocoService, gotten lazily via the Injector, to get the English translations. It also requires the use of a startup method via APP_INITIALIZER to load the English translations before the interceptor runs.
@josephmyers josephmyers requested a review from Nateowami May 19, 2025 06:13
@pmachapman pmachapman self-assigned this May 19, 2025
Copy link
Collaborator

@pmachapman pmachapman left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this new logic something that should be demonstrated in i18n-story.module.ts?

I think that could help given the subtleness of how a future update to transloco (I notice transloco has moved to a new npm package @jsverse/transloco) could inadvertently nerf this fix, as we rely on injecting into their dependency chain.

Reviewed 4 of 4 files at r1, all commit messages.
Reviewable status: all files reviewed, 8 unresolved discussions (waiting on @Nateowami)


src/SIL.XForge.Scripture/ClientApp/src/app/shared/ltr-marker.interceptor.spec.ts line 12 at r1 (raw file):

  const LRE = '\u202A';
  const PDF = '\u202C';

See comment above about defining the constants in utils.ts.

Code quote:

  const LRE = '\u202A';
  const PDF = '\u202C';

src/SIL.XForge.Scripture/ClientApp/src/app/app.module.ts line 101 at r1 (raw file):

    { provide: OverlayContainer, useClass: InAppRootOverlayContainer },
    provideHttpClient(withInterceptorsFromDi()),
    { provide: TRANSLOCO_INTERCEPTOR, useClass: LtrMarkerInterceptor },

Do think this line should be moved to xforge-common\xforge-common.module.ts where the other TRANSLOCO_ providers are?

You could alternatively move those providers out of there and put them here if you think that is more logical. My only real point is I think they should be together...somewhere.

Code quote:

{ provide: TRANSLOCO_INTERCEPTOR, useClass: LtrMarkerInterceptor },

src/SIL.XForge.Scripture/ClientApp/src/app/app-init.ts line 11 at r1 (raw file):

 * @returns A function that returns a Promise which resolves when English translations are loaded.
 */
export function preloadEnglishTranslations(translocoService: TranslocoService): () => Promise<any> {

NIT: Should this be Promise<Translation>?

Code quote:

Promise<any>

src/SIL.XForge.Scripture/ClientApp/src/app/app-init.ts line 11 at r1 (raw file):

 * @returns A function that returns a Promise which resolves when English translations are loaded.
 */
export function preloadEnglishTranslations(translocoService: TranslocoService): () => Promise<any> {

I am wondering whether this function should be in app/shared/utils.ts? Just seems odd to have a new file that just exports one small function.

Code quote:

preloadEnglishTranslations

src/SIL.XForge.Scripture/ClientApp/src/app/shared/ltr-marker.interceptor.ts line 25 at r1 (raw file):

  preSaveTranslation(translation: Translation, langOfTranslationFile: string): Translation {
    const activeUiLocale = I18nService.getLocale(langOfTranslationFile);
    const uiIsRtl = activeUiLocale?.direction === 'rtl';

NIT: Please define the types.

Code quote:

    const activeUiLocale = I18nService.getLocale(langOfTranslationFile);
    const uiIsRtl = activeUiLocale?.direction === 'rtl';

src/SIL.XForge.Scripture/ClientApp/src/app/shared/ltr-marker.interceptor.ts line 28 at r1 (raw file):

    if (uiIsRtl) {
      const englishTranslations = this.translocoService.getTranslation('en');

NIT: Please define the type.

Code quote:

const englishTranslations = this.translocoService.getTranslation('en');

src/SIL.XForge.Scripture/ClientApp/src/app/shared/ltr-marker.interceptor.ts line 45 at r1 (raw file):

      // This suggests the current value is an untranslated fallback to English.
      // Wrap with LTR markers if not already wrapped.
      if (!currentValue.startsWith('\u202A') && !currentValue.endsWith('\u202C')) {

You could define these constants as POP_DIRECTIONAL_FORMATTING and LEFT_TO_RIGHT_EMBEDDING along side export const RIGHT_TO_LEFT_MARK in utils.ts, then you would not need to define them in your spec.ts. (It would also make your code more readable here, as I forgot which one is which!)

Code quote:

f (!currentValue.startsWith('\u202A') && !currentValue.endsWith('\u202C')) {

src/SIL.XForge.Scripture/ClientApp/src/app/shared/ltr-marker.interceptor.ts line 57 at r1 (raw file):

    if (uiIsRtl) {
      const englishTranslations = this.translocoService.getTranslation('en');

NIT: Please define the types.

Code quote:

    const activeUiLocale = I18nService.getLocale(langOfValue);
    const uiIsRtl = activeUiLocale?.direction === 'rtl';

    if (uiIsRtl) {
      const englishTranslations = this.translocoService.getTranslation('en');

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
will require testing PR should not be merged until testers confirm testing is complete
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants