Inconsistent behaviour in i18n between markup and attributes

Given the two almost identical pieces of html

para 1

 <p 
    t="[html]tracking-report.span9" 
    t-params.bind="{
    rowNumber: item.rowNumber, 
    sick: item.sick, 
    dead: item.dead, 
    reason: trSicknessReason(item.reason)
    }">              
 </p>      

and

para 2

<p>
    ${"tracking-report.span9" & t:
    {
     rowNumber: item.rowNumber, 
     sick: item.sick, 
     dead: item.dead, 
     reason: trSicknessReason(item.reason)
    }
}.              
 </p>

// I added the extra line breaks to try to make the question clearer

My problem is that when i18n.locale() changes, para 1 does not update the function trSicknessReason(item.reason). However, in para 2 the function gets updated correctly when the locale changes.

The problem is that I need to unescape the result of the contents.

para 1 works perfectly well when the page is loaded and does the unescaping of the function through the use of the [html] attribute. However, as I said, the function doesn’t get updated when the locale changes.

I’m quite happy to use para 2 if only I can find a way to unescape the results. I’ve tried decodeURIComponent() on the return value of the function, but of course it immediately gets escaped when the page is loaded.

It doesn’t make sense that the interpolated value updates while the attribute value does not.

I guess it’s worth mentioning that trSicknessReason() does some fairly complicated lookups to find the correct value to translate.

In the worse case scenario I will have to prevent the user from entering illegal characters, but French is full of them :weary:

1 Like

Hey could you try to create a minimal example? That would help to analyse the issue

Hi. I’ve managed to extract the relevant parts.

The report and the sicknessReasons are retrieved from the database.

You don’t actually need the translations files but I included them for good measure.

Change the language to French to see the unescaped content.

Actual web site is at https://sti.amberwoodtrading.com. You need to register first. Then go to the tracking pool in the middle of the home page. The problem text is just under the main pictures. I had to change the French translation for d'Azote to de Azote :weary:

import { autoinject } from "aurelia-framework";
import { I18N } from "aurelia-i18n";

@autoinject
export class ErrorTrackingReport {
  public locales: IListItem[] = [
    { id: "en", name: "English" },
    { id: "fr", name: "French" },
    { id: "pt", name: "Portuguese" }
  ];

// dummy data from db
  public report: IInspectionReport = {
    id: undefined,
    dead: 3,
    sick: 12,
    rowNumber: 2,
    notes: "",
    reason: { id: "id-1", name: "default English value" }
  };

// dictionary items from db
  private sicknessReasons: ITranslations[] = [
    {
      id: "id-1",
      name: "Lack of nitrogen", // default value in English
      translations: [
        { id: "fr", name: "Manque d'Azote" },
        { id: "pt", name: "Falta de nitrogênio" }
      ]
    }
  ];

  constructor(private readonly i18n: I18N) {
  }

  public trSicknessReason(sicknessReason: IListItem) {
    const reason = this.sicknessReasons.find(c => c.id === sicknessReason.id);

    return this.trDictionaryTranslation(reason).toLowerCase();
  }

  public trDictionaryTranslation(item: ITranslations) {
    const locale = this.i18n.getLocale();
    const tr = item.translations.find(c => c.id === locale);

    if (tr && tr.name) {
      return tr.name;
    } else {
      return item.name;
    }
  }

  public async setLocale(lng: string) {
    await this.i18n.setLocale(lng);
  }
}

export interface IListItem {
  id: string;
  name: string;
}

export interface ITranslations extends IListItem {
  translations: IListItem[];
}

export interface IInspectionReport {
  dead: number;
  sick: number;
  reason: IListItem;
  rowNumber: number;
  notes: string;
  id: string;
  active?: boolean;
}

<template>
  <label>Language</label>
  <select class="form-contol" value.bind="selectedLng" change.delegate="setLocale(selectedLng)">
    <option repeat.for="lng of locales" model.bind="lng.id">${lng.name}</option>
  </select>

  <h2>This paragraph does not update when locale changes</h2>
  <p class="para-larger mb-0" t="[html]tracking-report.span9" t-params.bind="{rowNumber: report.rowNumber, sick: report.sick, dead: report.dead, reason: trSicknessReason(report.reason)}">
  </p>

  <h2 class="mt-5">This paragraph updates, but returns unescaped report.reason</h2>
  <p class="para-larger mb-0">
    ${"tracking-report.span9" & t: {rowNumber: item.rowNumber, sick: report.sick, dead: report.dead, reason: trSicknessReason(report.reason)}}.
  </p>

  <code>
    translations.en:
    {
      "tracking-report":
      {
        "span9":"In row {{rowNumber}}, there were {{sick}} sick and {{dead}} dead plants due to {{reason}}"
      }
    }


    translations.fr:
    {
      "tracking-report":
      {
        "span9":"Dans la rangée {{rowNumber}}, il y avait {{sick}} plantes malades et {{dead}} morts à cause de {{reason}}",
      }
    }


    translations.pt:
    {
      "tracking-report":
      {
        "span9":"Dans la rangée {{rowNumber}}, il y avait {{sick}} plantes malades et {{dead}} morts à cause de {{reason}}"
      }
    }

  </code>
</template>

Daniel - I’ve given you access to the site. Your email bounced!

So what would really help is to create a small, downloadable example, where I can dig in easily. This way its really more second guessing what could be wrong

OK - hope this is OK

https://github.com/jeremy-holt/translation-errors

That definitely helped. So the quick fix would be to go with option 2 and instead of interpolating as the value of the paragraph, binding it to the innerhtml attribute.

<p innerhtml.bind="'tracking-report.span9' & t: {rowNumber: report.rowNumber, sick: report.sick, dead: report.dead, reason: trSicknessReason(report.reason)}"></p>
  </p>

Your use case although is pretty special, so I think that a nested translation would also allow to do the same with Option 1

Update:
Here’s a patch of how I’d go about implementing it with Option 1 and nested translations

That’s great - thank you so much for your help.

Np glad i could help