PolymerElements / app-localize-behavior

Polymer behaviour to help internationalize your application
48 stars 55 forks source link

extending Polymer.AppLocalizeBehavior #97

Open sangron opened 7 years ago

sangron commented 7 years ago

Description

Creating a mixin/behavior to be extended by multiple elements, the this.loadResources('/src/locales.json') is not setting the resources property, so this.localize('res.key') returns undefined

Expected outcome

resources being set with the object in the json file

Actual outcome

resources stays undefined

Steps to reproduce

formats-behavior.html

<link rel="import" href="../../bower_components/app-localize-behavior/app-localize-behavior.html">

<script>
FormatMixin = function(superClass) {

  return class extends Polymer.mixinBehaviors([Polymer.AppLocalizeBehavior], superClass) {

    constructor() {
      super();
      this.loadResources(this.resolveUrl('/src/locales.json'));
    }

    static get properties() {
      return {
        language: {
          value: 'es'
        },
        formats: {
          type: Object,
          value: function() {
            return {
              number: { MXN: { style: 'currency', currency: 'MXN' } }
            };
          }
        }
      }
    }

    getLabel(a) {
      var args = Array.prototype.slice.call(arguments);
      var prop = args.join('');
      var label = prop;
      if(this.localize)
        label = this.localize(prop);

      return label;
    }

  }
}
</script>

then in file legal-docs.html

<link rel="import" href="mixins/formats-behavior.html">
<dom-module id="legal-docs">
  <template>
    <div>[[getLabel('app.title')]]</div>
  </template>
  <script>
    class LegalDocs extends FormatMixin(Polymer.Element) {

      static get is() { return 'legal-docs'; }

    }

    window.customElements.define(LegalDocs.is, LegalDocs);
  </script>
</dom-module>

and locales.json

{
  "es" : {
    "app.title" : "Doctos. Legales"
  }
}

I have debugged it and the constructor is being called, the json file is being loaded, confirming in the network tab in the devtools. But the resource property is not set. I have another app with this same pattern without problems, but it is using an earlier version of Polymer 2, so I am just guessing that something broke in recent changes.

Browsers Affected

notwaldorf commented 7 years ago

Have you tried loading the resources later, in ready or connectedCallback? constructor is way before Polymer stamped the properties, so it makes sense why resources is not populated

vtellier commented 7 years ago

Edit 27.06.2017: The issue happen when you don't lazy load your pages.

I have the same problem than @sangron: undefined resources attribute.

I implemented his code (I changed only names) to see what do we get when loading in constructor, ready in connectedCallback.

Listening to app-localize-resources-loaded

I used the event app-localize-resources-loaded to know when the resources are really loaded. I added few traces in the console to have an idea of the actual sequence:

FormatMixin:

    connectedCallback() {
      super.connectedCallback();

      console.log("Add a listener to app-localize-resources-loaded");
      this.addEventListener('app-localize-resources-loaded', function(e){
        console.log('app-localize-resources-loaded', this.resources);
      }.bind(this));

      console.log('Calling this.loadResources()...');
      this.loadResources(this.resolveUrl('/static/locales.json'));
    }

    static get properties() {
      // ...
    }

    getLabel(a) {

      console.log('getLabel() is called... this.resources is', this.resources);
      var args = Array.prototype.slice.call(arguments);
      var prop = args.join('');
      var label = prop;
      if(this.localize)
        label = this.localize(prop);

      return label;
    }

In the constructor

mi-localization.html:12 Add a listener to app-localize-resources-loaded mi-localization.html:17 Calling this.loadResources()... mi-localization.html:39 getLabel() is called... this.resources is undefined mi-localization.html:14 app-localize-resources-loaded Object {es: Object}

I added a <pre>{{resources}}</pre> so I can see if the binding works even though the call is performed in the constructor. I see the [Object object] rendered as expected because the result of the iron-ajax takes a bit of time and everything is ready-to-go when the response arrives. So in my opinion the properties will be stamped in most of cases.

In ready

getLabel() is called... this.resources is undefined mi-localization.html:12 Add a listener to app-localize-resources-loaded mi-localization.html:17 Calling this.loadResources()... mi-localization.html:14 app-localize-resources-loaded Object {es: Object}

The compound binding call the localize method even before we do request for the resources. The problem is then "how to delay the compound binding so we can trigger it when the resources arrived?"

In connectedCallback

getLabel() is called... this.resources is undefined mi-localization.html:12 Add a listener to app-localize-resources-loaded mi-localization.html:17 Calling this.loadResources()... mi-localization.html:14 app-localize-resources-loaded Object {es: Object}

The same...

Effective (but boring) workaround

To get it to work for sure, I use an intermediary private property in the compound binding instead of the synchronous call to the localize method. I feed this private property in the listener of app-localize-resources-loaded.

class OrdhusetApp extends LocalizationMixin(Polymer.Element) {
      static get is() { return 'ordhuset-app'; }
      static get properties() {
        return {
          __translation: {
            type: String,
            value: 'no translation :('
          }
        };
      }

    ready() {
        this.addEventListener('app-localize-resources-loaded', function(e){
          this.__translation = this.getLabel('app.title');
        }.bind(this));

        // Call the super.ready method after having set the listener
        // so i'm sure it is here before the resources arrive.
        super.ready();
      }
    }

And the compound binding:

<div>Test translation: [[__translation]]</div>

Result: image

It works... But it's not really convenient!

BennerGe commented 7 years ago

@notwaldorf What is the status of this issue? Still not working here.

rikbrowning commented 6 years ago

The issue is trying to load the resources globally in the app using load resources. Child components load and are created before the network request is returning. When the network request does finally return the _computedLocalize function never gets triggered in the child component as the resources have been updated outside of the child component so it is not aware of the changes. One potential solution could be that any time _computeLocalize gets called and there is an existing ajax request to listen for the response of it and update then. That way all localizations should be triggered when the one ajax response occurs.

dsyrstad commented 6 years ago

I find myself having to do this in all localized elements:

<dom-module id="my-element">
    <template>
       <style>...</style>
        <!-- Don't render until I18N resources are ready. -->
        <dom-if if="{{resources}}">
            <template>
                 ... all of my element's DOM...
            </template>
        </dom-if> <!-- resources -->
    </template>
   <script>
        class MyElement extends Polymer.mixinBehaviors([Polymer.AppLocalizeBehavior], ReduxActionsMixin(Polymer.Element)) {
    ...
   </script>
</dom-module>

This waits to construct any of the element's DOM until {{resources}} is non-empty/truthy. If I don't do this, I get localize() called before the resources are ready.

I'm using PolymerElements/app-localize-behavior 2.0.1.