3

For an I18n-like component, I want to get a component content as string to have a fallback value in case my I18n service gets nothing, this value is supposed to be a fallback one.

I18n service get method:

public get(key:string,
               defaultValue?:string,
               variables:Variables = {},
               domain?:string):string {

        for (let catalogue of this.catalogues) {
            if (catalogue.getDomains().indexOf(domain) >= 0
                && catalogue.getLocale() == this.locale
                && catalogue.hasKey(key)) {
                return I18n.setVariables(catalogue.get(key, domain, defaultValue).value, variables);
            }
        }
        return defaultValue;
    }

I18nComponent:

@Component({
    selector: "i18n",
    template: "{{text}}",
})
export class I18nComponent implements OnInit {

    constructor(private i18n:I18n) {
    }

    @ContentChild() content:string; //Here, I want to store actual value as fallback one.

    @Input('key') key:string;

    @Input('domain') domain:string;

    @Input('variables')
    variables:Variables = [];

    @Input("plural")
    plural:number;

    text:string;

    ngOnInit():any {
        console.log(this.content); //Here I get 'undefined'
        if (this.plural !== undefined && this.plural != null) {
            this.text = this.i18n.get(this.key, this.content, this.variables, this.domain);
        } else {
            this.text = this.i18n.getPlural(this.key, this.plural, this.content, this.variables, this.domain);
        }
    }
}

usage example:

<i18n key="FOO_KEY" domain="stackoverflow">I'm the default value !</i18n>

I know <ng-content></ng-content> works but only on the template logic, I need a way to get child as string in typescript.

Already tried @ContentChild(String) but I got undefined.

Another way would be to do it like "Loading..." string you can have in the base app component in the index, acting like a placeholder until you get everything loaded. I could do the same for I18n, let the laceholder here until I get something from service to replace it, but I don't know how to achieve this.

Günter Zöchbauer
  • 623,577
  • 216
  • 2,003
  • 1,567
Supamiu
  • 8,501
  • 7
  • 42
  • 76
  • I don't get what you try to accomplish. How is this related to your I18n service? Why don't just just put the fallback value into the class as well and then bind the one from the service and if not available, the fallback? – Günter Zöchbauer Jun 01 '16 at 15:57
  • I can't set the fallback value in the class because it's not the same everytime. I have one fallback for each i18n component usage, and it may include html tags. (example: `You won` -> fallback = `You won`). The fallback is here "just in case", then, if I don't get the value throught the service, I'll display the english one (the fallback). – Supamiu Jun 01 '16 at 16:36
  • Can you please add a full example. I don't get from your question what exactly you try to accomplish. – Günter Zöchbauer Jun 01 '16 at 16:38
  • @GünterZöchbauer I updated question to provide a full example. – Supamiu Jun 02 '16 at 07:48

1 Answers1

4

You can't use @ContentChildren() to get the whole content. You can either add a template variable and query for its name:

<i18n key="FOO_KEY" domain="stackoverflow"><span #myVar>I'm the default value !</span></i18n>
@ContentChild('myVar') myVar;

ngAfterContentInit() {
  console.log(this.myVar.nativeElement.innerHTML);
}

myVar will not be initialized before ngAfterContentInit() is called.

Alternatively @ContentChild() (or @ContentChildren()) you can query for a components type like

    <i18n key="FOO_KEY" domain="stackoverflow"><my-comp>I'm the default value !</my-comp></i18n>

@ContentChild(MyComponent, {read: ElementRef}) mycomp;

ngAfterContentInit() {
  console.log(this.myVar.nativeElement.innerHTML);
}

I think this approach will work better for you approach

@Component({
    selector: "i18n",
    template: "<div #wrapper hidden="true"><ng-content></ng-content><div>{{text}}",
})
export class I18nComponent implements OnInit {

    constructor(private i18n:I18n) {
    }

    @ViewChild('wrapper') content:ElementRef; //Here, I want to store actual value as fallback one.

    @Input('key') key:string;

    @Input('domain') domain:string;

    @Input('variables')
    variables:Variables = [];

    @Input("plural")
    plural:number;

    text:string;

    ngAfterViewInitInit():any {
        console.log(this.content.nativeElement.innerHTML);
        if (this.plural !== undefined && this.plural != null) {
            this.text = this.i18n.get(this.key, this.content, this.variables, this.domain);
        } else {
            this.text = this.i18n.getPlural(this.key, this.plural, this.content, this.variables, this.domain);
        }
    }
}

If you want the users of your i18n component to be able to use Angular bindings, components, and directives, in the content they pass to <i18n>, then he needs to wrap it in a template.

    <i18n key="FOO_KEY" domain="stackoverflow">
      <template>
        I'm the {{someName}}!
        <my-comp (click)="doSomething()"></my-comp>
      </template>
    </i18n>

like explained in https://stackoverflow.com/a/37229495/217408

Kurt Kriegler
  • 35
  • 1
  • 6
Günter Zöchbauer
  • 623,577
  • 216
  • 2,003
  • 1,567