Skip to content

MdPaginatorIntl Internationalization example #5573

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

Closed
Laurensvdw opened this issue Jul 7, 2017 · 36 comments
Closed

MdPaginatorIntl Internationalization example #5573

Laurensvdw opened this issue Jul 7, 2017 · 36 comments

Comments

@Laurensvdw
Copy link

The new paginator has a small text in the documentation about Internationalization. I was wondering if anyone has an example of doing this. The document states that you can do this by "providing your own instance of MdPaginatorIntl". I have no idea how to do this... Thanks!

@jelbourn
Copy link
Member

jelbourn commented Jul 7, 2017

Please keep GitHub issues for bug reports / feature requests. Better avenues for troubleshooting / questions are stack overflow, gitter, mailing list, etc.

@jelbourn jelbourn closed this as completed Jul 7, 2017
@CaerusKaru
Copy link
Member

Although this is not explicitly mentioned in the docs, you can infer the purpose of MdPaginatorIntl because it's listed as an Injectable class. This means that it can be leveraged with Angular Dependency Injection (DI), which you can read up on here.

When the docs say you must provide your own instance, it means extending the MdPaginatorIntl class, and then overriding the existing implementation in your component in the providers section.

@ferlellws
Copy link

Try in oninit this:
this.paginator._intl.itemsPerPageLabel = 'Registros por página';

@Laurensvdw
Copy link
Author

Okay I solved this by using each of your solutions ;)
Adding MdPaginator to providers array =>
providers: [MdPaginator]
In the constructor inject the paginator =>
private paginator: MdPaginator
In the OnInit cycle (using translateService of ng2-translate) =>

this.translate.get('app.pagination.itemsPerPageLabel').subscribe(translation => {
   this.paginator._intl.itemsPerPageLabel = translation
});

@CaerusKaru
Copy link
Member

_intl is a private data member on MdPaginator for a reason: it shouldn't be modified directly. Please follow the documentation to create your own instance of the provider, instead of violating abstraction barriers.

@shlomiassaf
Copy link
Contributor

@Laurensvdw, actually @CaerusKaru is right but in your defence I can say you just couldn't implement it properly since MdPaginatorIntl is not exposed.

@Laurensvdw
Copy link
Author

Okay that explains why I couldn't get it working with the documentation @CaerusKaru mentioned earlier about DI, as the MdPaginatorIntl was not available to place in the providers... Thanks for your findings @shlomiassaf !

@jelbourn
Copy link
Member

Ah, now that is a bug. I sent #5726 to fix.

@Lior-G
Copy link

Lior-G commented Jul 14, 2017

I'm still unclear as to the process for this.

what I understand; and please correct me wherever I'm wrong

  • create a new file. Let's call it: paginator.provider.ts
    • import { MdPaginatorIntl } from ... not really sure
  • create a new Injectable similar to the original; but which extends MdPaginatorIntl
  • on your app.modules.ts then import your own version of the instance, lets call it: MdPaginatorProvider
  • add (or update) your providers array to include your instance
  • add it to the app constructor:
    • private mdPaginatorProvider: MdPaginatorProvider

what else?

thank you in advanced

@willshowell
Copy link
Contributor

@Lior-G see https://plnkr.co/edit/Zy0kOh78CBelkBH8VEug?p=preview. I don't know if that's the best way to do it, but it works

@Lior-G
Copy link

Lior-G commented Jul 20, 2017

@willshowell
what version of Angular Material are you using?
MdPaginatorIntl is not exposed in in 2.0.0-beta.8 which currently is the latest release

@CaerusKaru
Copy link
Member

CaerusKaru commented Jul 20, 2017

@Lior-G The Plunkr uses the latest build from the master branch (generally a couple of commits behind the latest commit). MdPaginatorIntl is exposed as of #5716.

You can have your project rely on the latest build by following Step 1 in the Getting Started guide, just run:
npm install --save angular/material2-builds angular/cdk-builds

@diarcastro
Copy link

diarcastro commented Aug 10, 2017

Hi, MdPaginatorIntl is exported as ex on (@angular/material/typings/index.d.ts) you can import this class like this:

import {
  ɵx as MdPaginatorIntl
} from '@angular/material';

And after:

@NgModule({
  declarations: [
  ],
  imports: [
    { provide: MdPaginatorIntl, useClass: MyClassIntl }
  ]
  , bootstrap: [AppComponent]
})
export class AppModule { }

//MyClassIntl

export class MyClassIntl {
  itemsPerPageLabel = 'Registros por página: ';
  nextPageLabel = 'Página siguiente';
  previousPageLabel = 'Página anterior';

  getRangeLabel(page: number, pageSize: number, length: number): string {
    if (length === 0 || pageSize === 0) {
      return `0 de ${length}`;
    }
    length = Math.max(length, 0);
    const startIndex = page * pageSize;
    // If the start index exceeds the list length, do not try and fix the end index to the end.
    const endIndex = startIndex < length ?
      Math.min(startIndex + pageSize, length) :
      startIndex + pageSize;
    return `${startIndex + 1} - ${endIndex} de ${length}`;
  }
}

@zggb
Copy link

zggb commented Oct 26, 2017

The cool example that @diarcastro provide unfortunately is not working on beta.10

seems that /typings/index.d.ts in beta.10 has't
export { MdPaginatorIntl as ɵx } from './paginator/paginator-intl';

@mhosman
Copy link

mhosman commented Dec 24, 2017

The documentation to do this is really poor. Anybody with an example for creating your own class with internationalization using for example ngx-translate? Something DYNAMIC ???

@rsaulo
Copy link

rsaulo commented Jan 16, 2018

+1 to an example how to make this DYNAMIC!!! We use ngx-translate and cannot make this single line of the table dynamic...

@rivoheinsalu
Copy link

rivoheinsalu commented Jan 17, 2018

@mhosman @rsaulo This is how I did it in "@angular/material": "^5.0.3",

Create separate file e.g. custom-mat-paginator-int.ts:

import {TranslateService} from '@ngx-translate/core';
import {MatPaginatorIntl} from '@angular/material';
import {Injectable} from '@angular/core';

@Injectable()
export class CustomMatPaginatorIntl extends MatPaginatorIntl {
  constructor(private translate: TranslateService) {
    super();

    this.translate.onLangChange.subscribe((e: Event) => {
      this.getAndInitTranslations();
    });

    this.getAndInitTranslations();
  }

  getAndInitTranslations() {
    this.translate.get(['ITEMS_PER_PAGE', 'NEXT_PAGE', 'PREVIOUS_PAGE', 'OF_LABEL']).subscribe(translation => {
      this.itemsPerPageLabel = translation['ITEMS_PER_PAGE'];
      this.nextPageLabel = translation['NEXT_PAGE'];
      this.previousPageLabel = translation['PREVIOUS_PAGE'];
      this.changes.next();
    });
  }

 getRangeLabel = (page: number, pageSize: number, length: number) =>  {
    if (length === 0 || pageSize === 0) {
      return `0 / ${length}`;
    }
    length = Math.max(length, 0);
    const startIndex = page * pageSize;
    const endIndex = startIndex < length ? Math.min(startIndex + pageSize, length) : startIndex + pageSize;
    return `${startIndex + 1} - ${endIndex} / ${length}`;
  }
}

Load and include it in providers in app.module.ts

import {CustomMatPaginatorIntl} from './shared/custom-mat-paginator-intl';

 providers: [{
      provide: MatPaginatorIntl, 
      useClass: CustomMatPaginatorIntl
    }]

@rsaulo
Copy link

rsaulo commented Jan 17, 2018

@rivoheinsalu , yes, i ended with the same solution, thanks for the reply!

@mhosman
Copy link

mhosman commented Jan 17, 2018

Hey @rivoheinsalu, thanks for sharing. The only problem about this is that it keeps saying for example 1 of 2. How can I change the "of"?

@rivoheinsalu
Copy link

@mhosman I have edited my example, see getRangeLabel.

I have not tried it with 'of', insted changed it to '/'.

You could try to set private properti and assing value in translation part:
this.ofLabel = translation['OF_LABEL']

And then try to use it in getRangeLabel(). Unfortunately I cannot currently test it.

@mhosman
Copy link

mhosman commented Jan 17, 2018

Great @rivoheinsalu !! Thank you very much for your help!

@mhosman
Copy link

mhosman commented Jan 17, 2018

Sorry I have just one last problem... I have another component with preferences. In that component I change the language. Then, I just go back to the component with the paginator and the language is not being changed (just in the paginator, in the rest of the site is working ok). The onLangChange is not working or maybe because I change the language in other component, the onLangChange is not being fired... so... the only way is to create a custom observable??

@rivoheinsalu
Copy link

@mhosman did you include following to providers in global (app.module.ts) as I told not in component?

providers: [{ provide: MatPaginatorIntl, useClass: CustomMatPaginatorIntl }]

For me it works well when changing language in one component and then navigating to component with table.

Second this.getAndInitTranslations(); in CustomMatPaginatorIntl should get translations on first load with current language and onLangChange should trigger this.getAndInitTranslations(); only when language is changed while table component is already open.

Try debugging or console logging in CustomMatPaginatorIntl and see if second this.getAndInitTranslations() is even invoked, hope I am not wrong, but I think the problem comes down to your setup somewhere.

@mhosman
Copy link

mhosman commented Jan 18, 2018

Yes @rivoheinsalu all setup it's okay. Paginator is working okay and current language it's okay. The only problem is with onLangChange. It's not being fired (I also put a console.log inside and nothing). Important: all the rest of the site is changing the language okay, the only problem is with the onLangChange inside this CustomMatPaginatorIntl.

@dani3lrp
Copy link

dani3lrp commented Feb 9, 2018

In my table I only added the following in the ngOnInit():

ngOnInit() {
this.paginator._intl.itemsPerPageLabel = 'Registros por página';
this.paginator._intl.nextPageLabel = 'Siguiente';
this.paginator._intl.previousPageLabel = 'Anterior';
}

@duard
Copy link

duard commented Feb 23, 2018

@dani3lrp how an I translate "1 of 10" ?

@duard
Copy link

duard commented Feb 23, 2018

@rivoheinsalu I got No provider for TranslateService! with your solution

@rivoheinsalu
Copy link

@duard Have you properly setup ngx-translate module importing TranslateModule.forRoot()?
See documentation from here: https://github.com/ngx-translate/core

@AnimaWolf
Copy link

Extending on @rivoheinsalu his answer (with 'OF' translation & unsubscribing from translate)

import { Injectable, OnDestroy } from '@angular/core';
import { MatPaginatorIntl } from '@angular/material';
import { TranslateService } from '@ngx-translate/core';
import { Subject } from 'rxjs/Subject';

@Injectable()
export class CustomMatPaginatorIntl extends MatPaginatorIntl implements OnDestroy {

  unsubscribe: Subject<void> = new Subject<void>();
  OF_LABEL = 'of';

  constructor(private translate: TranslateService) {
    super();

    this.translate.onLangChange.takeUntil(this.unsubscribe).subscribe(() => {
      this.getAndInitTranslations();
    });

    this.getAndInitTranslations();
  }

  ngOnDestroy() {
    this.unsubscribe.next();
    this.unsubscribe.complete();
  }

  getAndInitTranslations() {
    this.translate.get([
      'PAGINATOR.ITEMS_PER_PAGE',
      'PAGINATOR.NEXT_PAGE',
      'PAGINATOR.PREVIOUS_PAGE',
      'PAGINATOR.OF_LABEL'
    ])
      .takeUntil(this.unsubscribe)
      .subscribe(translation => {
        this.itemsPerPageLabel = translation['PAGINATOR.ITEMS_PER_PAGE'];
        this.nextPageLabel = translation['PAGINATOR.NEXT_PAGE'];
        this.previousPageLabel = translation['PAGINATOR.PREVIOUS_PAGE'];
        this.OF_LABEL = translation['PAGINATOR.OF_LABEL'];
        this.changes.next();
      });
  }

  getRangeLabel = (page: number, pageSize: number, length: number) => {
    if (length === 0 || pageSize === 0) {
      return `0 ${this.OF_LABEL} ${length}`;
    }
    length = Math.max(length, 0);
    const startIndex = page * pageSize;
    const endIndex = startIndex < length ? Math.min(startIndex + pageSize, length) : startIndex + pageSize;
    return `${startIndex + 1} - ${endIndex} ${this.OF_LABEL} ${length}`;
  };
}

Everything else is exactly the same as specified in Rivo's answer

@ThibaudAV
Copy link

Here is my test file for the code above. If you like to have code coverage 💯 :

import { TestBed } from '@angular/core/testing';
import { async } from '@angular/core/testing';
import { TranslateCompiler, TranslateFakeCompiler, TranslateModule, TranslateService } from '@ngx-translate/core';
import { CustomMatPaginatorIntl } from './custom-matPaginatorIntl.service';

describe('CustomMatPaginatorIntl', () => {
  let service: CustomMatPaginatorIntl;
  let translateService: TranslateService;

  beforeEach(async(() => {
    TestBed.configureTestingModule({
      providers: [CustomMatPaginatorIntl],
      imports: [
        TranslateModule.forRoot({
          compiler: { provide: TranslateCompiler, useClass: TranslateFakeCompiler },
        }),
      ],
    });
    translateService = TestBed.get(TranslateService);
    translateService.langs = ['fr', 'en'];
    translateService.currentLang = 'fr';
    translateService.setTranslation('en', { 'translation.paginator.items-per-page': 'Item per page' });
    translateService.setTranslation('fr', { 'translation.paginator.items-per-page': 'Éléments par page' });

    service = TestBed.get(CustomMatPaginatorIntl);
  }));

  it('should be created', () => {
    expect(service).toBeTruthy();
  });

  it('should get the translation from TranslateService', () => {
    expect(service.itemsPerPageLabel).toEqual('Éléments par page');
    expect(service.nextPageLabel).toEqual('translation.paginator.next-page');
    expect(service.previousPageLabel).toEqual('translation.paginator.previous-page');
    expect(service.ofLabel).toEqual('translation.paginator.of-label');
    expect(service.getRangeLabel(1, 10, 20)).toEqual('11 - 20 translation.paginator.of-label 20');
  });

  describe('when language change', () => {
    it('should changes the translation language', (done: any) => {
      service.changes.subscribe(() => {
        expect(service.itemsPerPageLabel).toEqual('Item per page');
        done();
      });
      translateService.use('en');
    });
  });
});

@djidel
Copy link

djidel commented Oct 1, 2018

@AnimaWolf in my case ngOnDestroy never get called, have you tested your implementation of OnDestroy?

@broweratcognitecdotcom
Copy link

Are they joking with this??? Refactor. Provide simple inputs. Please.

@gotwig
Copy link

gotwig commented Mar 8, 2019

WoW- I just found one of the ugliest sides of angular material...

@knoefel
Copy link

knoefel commented Apr 18, 2019

The subscription to this.translate.onLangChange is unnecessary when you use this.translate.stream instead of this.translate.get, because it will execute everytime the language changes. So the following implementation is the same as @AnimaWolf's answer, but a little shorter:

`

import { MatPaginatorIntl } from "@angular/material";
import { TranslateService } from "@ngx-translate/core";
import { OnDestroy, Injectable } from "@angular/core";
import { takeUntil } from "rxjs/operators";
import { Subject } from "rxjs";

@Injectable()
export class MatPaginatorI18n extends MatPaginatorIntl implements OnDestroy {
    private onDestroy$: Subject<boolean> = new Subject();

    constructor(private readonly translate: TranslateService) {
        super();

        this.translate
            .stream([
                "MATERIAL.PAGINATOR.ITEMS_PER_PAGE",
                "MATERIAL.PAGINATOR.NEXT_PAGE",
                "MATERIAL.PAGINATOR.PREVIOUS_PAGE",
                "MATERIAL.PAGINATOR.FIRST_PAGE",
                "MATERIAL.PAGINATOR.LAST_PAGE"
            ])
            .pipe(takeUntil(this.onDestroy$))
            .subscribe(translations => {
                this.itemsPerPageLabel = translations["MATERIAL.PAGINATOR.ITEMS_PER_PAGE"];
                this.nextPageLabel = translations["MATERIAL.PAGINATOR.NEXT_PAGE"];
                this.previousPageLabel = translations["MATERIAL.PAGINATOR.PREVIOUS_PAGE"];
                this.firstPageLabel = translations["MATERIAL.PAGINATOR.FIRST_PAGE"];
                this.lastPageLabel = translations["MATERIAL.PAGINATOR.LAST_PAGE"];
                this.getRangeLabel = this.getRangeLabel.bind(this);

                this.changes.next();
            });
    }

    ngOnDestroy(): void {
        this.onDestroy$.next();
        this.onDestroy$.complete();
    {


    getRangeLabel = (page: number, pageSize: number, length: number): string => {
        if (length === 0 || pageSize === 0) {
            return this.translate.instant("MATERIAL.PAGINATOR.RANGE_LABEL_NO_ITEMS", { length });
        }

        length = Math.max(length, 0);
        
        const startIndex = page * pageSize;
        // If the start index exceeds the list length, do not try and fix the end index to the end.
        const endIndex = startIndex < length ? Math.min(startIndex + pageSize, length) : startIndex + pageSize;
        return this.translate.instant("MATERIAL.PAGINATOR.RANGE_LABEL", {
            startIndex: startIndex + 1,
            endIndex,
            length
        });
    }
}

`

@angular-automatic-lock-bot
Copy link

This issue has been automatically locked due to inactivity.
Please file a new issue if you are encountering a similar or related problem.

Read more about our automatic conversation locking policy.

This action has been performed automatically by a bot.

@angular-automatic-lock-bot angular-automatic-lock-bot bot locked and limited conversation to collaborators Sep 10, 2019
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests