storybookjs / storybook

Storybook is the industry standard workshop for building, documenting, and testing UI components in isolation
https://storybook.js.org
MIT License
84.66k stars 9.32k forks source link

ngx-translate integration #6541

Closed androdel closed 5 years ago

androdel commented 5 years ago

Hi,

How do I integrate ngx-translate so I can use it with Storybook for Angular?

Kind regards, androdel

kroeder commented 5 years ago

What have you tried so far? A colleague implemented it in our mono-repo but as far as I can see we needed to do some extra stuff in our story

import { FakeMissingTranslationHandler, MissingTranslationHandler, TranslateLoader } from '@ngx-translate/core';
import { storiesOf } from '@storybook/angular';
import { of } from 'rxjs';
import { I18nModule } from '../i18n.module';
import { I18nDemoComponent } from './i18n-demo.component';

// symbols used in decorators need to be exported
export const staticTranslateLoader: TranslateLoader = {
    getTranslation(lang: string) {
        return of(require('./i18n/en.json'));
    }
};

storiesOf('Foundation', module)
    .add(
        'I18n',
        () => ({
            component: I18nDemoComponent,
            moduleMetadata: {
                imports: [
                    I18nModule.forRoot({
                        loader: {
                            provide: TranslateLoader,
                            useValue: staticTranslateLoader
                        },
                        missingTranslationHandler: {
                            provide: MissingTranslationHandler,
                            useClass: FakeMissingTranslationHandler
                        }
                    })
                ]
            }
        })
    );

Note: I18nModule is something we added. Replace this code with TranslationModule from ngx-translate. Maybe it works right away 🙂

If this does not solve your issue, we configured it like that:

deepaksslibra commented 5 years ago

Hello I seem to be facing the same issue as well.. I tried the method above but it didn't work

kroeder commented 5 years ago

I did a fresh Angular installation and integrated ngx-translate into the actual angular app + into storybook https://github.com/kroeder/storybook-ngx-translate

I also added a library project but haven't tried integrating ngx-translate there yet.

Please let me know if this is of any help

A couple of notes:

storiesOf('Button', module).add('with text', () => ({
  template: `
    <button>{{ 'basic.submit' | translate }}</button>
  `,
  moduleMetadata: {
    imports: [I18nModule]
  }
}));
androdel commented 5 years ago

Ofcourse, putting the code in a module would be the answer (stupid me). Anyway, this raises another issue and that is that he cannot find my en.json file, but it is present.

Schermafbeelding 2019-04-21 om 09 53 20 Schermafbeelding 2019-04-21 om 09 58 02

PS: I'm working in an Angular 6 app

kroeder commented 5 years ago

@androdel is it in src/assets or projects/your-project/src/assets ? If you need a custom assets folder then try using https://storybook.js.org/docs/configurations/serving-static-files/#2-via-a-directory

androdel commented 5 years ago

@kroeder it's in src/assets

I now also started with a clean build and it works! Could the issue be Angular 6 related?

kroeder commented 5 years ago

I don't think so. They might have added something to the default angular.json when bootstrapping a new angular app. Can you try to upgrading to 7 using ng update @angular/cli @angular/core in a branch?

https://angular.io/cli/update#ng-update

deepaksslibra commented 5 years ago

@kroeder

For some reason, i18nModule doesn't work out of the box. I had to use translate.setDefaultLang('en'); translate.use('en'); everywhere inside the constructor of the Components for which I was creating the story. Do you have any idea why that would be the case ?

kroeder commented 5 years ago

@deepaksslibra https://github.com/kroeder/storybook-ngx-translate/blob/master/src/app/i18n/i18n.module.ts#L26 I did it in the constructor of my I18nModule

This does not work for you?

stale[bot] commented 5 years ago

Hi everyone! Seems like there hasn't been much going on in this issue lately. If there are still questions, comments, or bugs, please feel free to continue the discussion. Unfortunately, we don't have time to get to every issue. We are always open to contributions so please send us a pull request if you would like to help. Inactive issues will be closed after 30 days. Thanks!

Txuso commented 5 years ago

Hi everyone,

I managed to make it work combining some of your solutions. The key is what @kroeder was suggesting of creating a "fake" I18 module and import it in the moduleMetadata. Moreover, the staticTranslateLoader can be used to handle translations within the component. Here I paste the working config that should be added to you component stories. carbon

Thanks to everyone and I hope you have a nice day :-)

stale[bot] commented 5 years ago

Hi everyone! Seems like there hasn't been much going on in this issue lately. If there are still questions, comments, or bugs, please feel free to continue the discussion. Unfortunately, we don't have time to get to every issue. We are always open to contributions so please send us a pull request if you would like to help. Inactive issues will be closed after 30 days. Thanks!

stale[bot] commented 5 years ago

Hey there, it's me again! I am going close this issue to help our maintainers focus on the current development roadmap instead. If the issue mentioned is still a concern, please open a new ticket and mention this old one. Cheers and thanks for using Storybook!

gerardcastell commented 4 years ago

Thank you so much for the solution! Though, diving deeper I stumbled upon a problem. I started to use this feature with the 'StoriesOf' function, but eventually, I had to pass to an arrow function approach to build the story. The reason is, basically, for combine Knobs with the translate pipe and it works fine! Unfortunately, with the template on the arrow functions inputs works properly whilst actions don't! I leave an example below, thank you! export const primary = () => ({ title: "UiEmptyScreen", moduleMetadata: { imports: [I18nModule], declarations: [UiEmptyScreenComponent], providers: [TranslateService] }, component: UiEmptyScreenComponent, template:<presenter-empty-screen [title]="title | translate" [subtitle]="subtitle | translate" [icon]="icon | translate" [buttonTitle]="buttonTitle | translate" (clickedButton)="clickedButton"

, props: { title: text("title", "dashboard.claim.empty_screen.TITLE"), subtitle: text("subtitle", "dashboard.claim.empty_screen.SUBTITLE"), icon: text("icon", "dashboard.claim.empty_screen.ICON"), buttonTitle: text( "buttonTitle", "dashboard.claim.empty_screen.BUTTON_TITLE" ), clickedButton: action("clickedButton event") } });

Component.ts: `import { Component, Input, Output, EventEmitter } from "@angular/core";

@Component({ selector: "presenter-empty-screen", templateUrl: "./ui-empty-screen.component.html", styleUrls: ["./ui-empty-screen.component.scss"] }) export class UiEmptyScreenComponent { @Input() title: string; @Input() subtitle: string; @Input() icon: string; @Input() buttonTitle: string; @Output() clickedButton = new EventEmitter();

constructor() {}

onClick(): void { this.clickedButton.emit(); } } `

gerardcastell commented 4 years ago

I've solved it! I attach the code below. Anyway, I would like to be capable of switch language translate with a knob, is it possible? Thanks! export const englishComponent = () => ({ moduleMetadata: { imports: [I18nModule], declarations: [UiEmptyScreenComponent], providers: [TranslateService] }, component: UiEmptyScreenComponent, template:<presenter-empty-screen [title]="title | translate" [subtitle]="subtitle | translate" [icon]="icon | translate" [buttonTitle]="buttonTitle | translate" (clickedButton)="clickedButton()"

, props: { title: text("title", "dashboard.claim.empty_screen.TITLE"), subtitle: text("subtitle", "dashboard.claim.empty_screen.SUBTITLE"), icon: text("icon", "dashboard.claim.empty_screen.ICON"), buttonTitle: text( "buttonTitle", "dashboard.claim.empty_screen.BUTTON_TITLE" ), clickedButton: () => TranslateService.use("es") } });

dsebastien commented 4 years ago

I've written a small article about this subject, in case anyone is looking for help: https://medium.com/@dSebastien/using-ngx-translate-in-storybook-stories-3f4228f80e02

danielquintero commented 4 years ago

@dsebastien i have a nx monorepo angular + storybook setup, i tried what you described in your blog but i still get error because it cannot resolve that pipe translate on the templates. any clues?


// THE STORYBOOK NGX TRANSLATE CONFIG MODULE
import { TranslateModule, TranslateService, TranslateLoader } from "@ngx-translate/core"
import { TranslateHttpLoader } from '@ngx-translate/http-loader';
import { NgModule } from '@angular/core';
import { HttpClient, HttpClientModule } from '@angular/common/http';

export function HttpLoaderFactory(httpClient: HttpClient) {
    return new TranslateHttpLoader(httpClient, './assets/i18n/', '.json');
}

@NgModule({
  imports: [
    HttpClientModule,
    TranslateModule.forRoot({
      loader: {
        provide: TranslateLoader,
        useFactory: HttpLoaderFactory,
        deps: [HttpClient],
      },
    })
  ],
  providers: [TranslateService]
})
export class SBTranslateModule {
  constructor(translateService: TranslateService) {
    console.log("Configuring the translation service: ", translateService);
    console.log("Translations: ", translateService.translations);
    translateService.setDefaultLang("en-US");
    translateService.use("en-US");
  }
}

// THE STORYBOOK STORY
...
export default {
  title: 'HeaderComponent',
  decorators:[
    moduleMetadata({
      declarations:[HeaderComponent],
      imports: [SBTranslateModule],
      providers: [{ provide: APP_BASE_HREF, useValue: '/' }],
    }),
  ]
};

const label = 'title';
const defaultValue = {
    title: 'back',
    routerLink: '/',
};
const groupId = 'GROUP-ID1';

export const Default = () => ({
    props: {
        title: text('text', 'Header!'),
        back: object(label, defaultValue, groupId),
  },
  component: HeaderComponent,
});
dsebastien commented 4 years ago

I think I know why. You still need to import the TranslateModule, not only for .forRoot. You can do it like this for example:

@NgModule({
  imports: [
    HttpClientModule,
    TranslateModule.forRoot({
      loader: {
        provide: TranslateLoader,
        useFactory: HttpLoaderFactory,
        deps: [HttpClient],
      },
    }),
    TranslateModule,
  ],
  providers: [TranslateService]
})
export class SBTranslateModule {
  constructor(translateService: TranslateService) {
    console.log("Configuring the translation service: ", translateService);
    console.log("Translations: ", translateService.translations);
    translateService.setDefaultLang("en-US");
    translateService.use("en-US");
  }
}

In my case I didn't need it because I have a CoreModule which takes care of it. I'll adapt the blog post to mention it! :)

ivayloc commented 3 years ago

I want to create a story where I can change the Ngx default language, in an Angular app usually, this is done through Ngx TranslateService, any ideas on how to use this service in a story?

shilman commented 3 years ago

Son of a gun!! I just released https://github.com/storybookjs/storybook/releases/tag/v6.3.0-alpha.7 containing PR #14226 that references this issue. Upgrade today to the @next NPM tag to try it out!

npx sb upgrade --prerelease
Stusaw commented 2 years ago

I've worked my way through of the examples above and have managed to get it 'working' intermittently. However, this does require that the default language is set it the contructor of the i18n module and when the story is refreshed (F5) it reverts back to en-GB.

Has anyone actually got this working were by you can select the local from the toolbar in Storybook and set the local using TranslateService? I am new to SB but cannot see a way to hook into the changes in the globalTypes for me then to be able to set the locale on the fly?

NB: Using Angular 13

leekFreak commented 2 years ago

@Stusaw did you find an answer to this? I'm also struggling with the same issues. The solutions I found all seem to apply to React, not Angular.

Stusaw commented 2 years ago

@Stusaw did you find an answer to this? I'm also struggling with the same issues. The solutions I found all seem to apply to React, not Angular.

Unfortunately not via the SB UI. I did however build a 'translate' button component and included that as part of my story (Eg. in the main navigation toolbar). As I was building a composite 'full-page' story this worked for me.

leekFreak commented 2 years ago

@Stusaw that could be a workaround, but not very practical with a lot of components to test in my library. I got the language button to display in StoryBook with adding to preview.js locales: { en: "English", fr: "Français", es: "Espanol" },

But I can't figure out to grab the new value and set the TranslateService accordingly. Clicking on another language has no effect.

yanesteves commented 2 years ago

I was able to use ngx-translate just by creating a specific module for the storybook and adding the translation configuration. My storybook.module.ts:

import { TranslateModule, TranslateService, TranslateLoader } from "@ngx-translate/core"
import { TranslateHttpLoader } from '@ngx-translate/http-loader';
import { LOCALE_ID, NgModule } from '@angular/core';
import { HttpClient, HttpClientModule } from '@angular/common/http';
import { CommonModule, registerLocaleData } from "@angular/common";

export function HttpLoaderFactory(http: HttpClient) {
  return new TranslateHttpLoader(http, "../assets/i18n/", ".json")
}
import localePt from '@angular/common/locales/pt';
registerLocaleData(localePt);

@NgModule({
  declarations: [],
  imports: [
    HttpClientModule,
    CommonModule,
    TranslateModule.forRoot({
      loader: {
        provide: TranslateLoader,
        useFactory: HttpLoaderFactory,
        deps: [HttpClient]
      },
    }),
  ],
  exports: [TranslateModule],
  providers: [
    { provide: LOCALE_ID, useValue: 'pt'}
  ]
})
export class StorybookModule { 
  constructor(translate: TranslateService) {
    translate.setDefaultLang("pt");
    translate.use("pt")
  }
}

My component.story.ts


export default {
    title: 'Shape-U/Components/Lista',
    component: ListaBaseComponent,
    decorators: [
        moduleMetadata({
            imports: [StorybookModule],
        })
    ]
} as Meta;
Jaclawiciel commented 1 year ago

@Stusaw did you find an answer to this? I'm also struggling with the same issues. The solutions I found all seem to apply to React, not Angular.

@Stusaw, @leekFreak I figure out the solution and it's surprisingly simple!

So I guess you set the default language in the module's constructor like in the previous answers. This is the place where you can add listener for Storybook's globals updates. The whole module declaration can look like this. The last few lines of the constructor is where the magic happens.

import { TranslateModule, TranslateService, TranslateLoader } from "@ngx-translate/core"
import { TranslateHttpLoader } from '@ngx-translate/http-loader';
import { LOCALE_ID, NgModule } from '@angular/core';
import { HttpClient, HttpClientModule } from '@angular/common/http';
import { CommonModule, registerLocaleData } from "@angular/common";
import { addons } from "@storybook/addons";
import { GLOBALS_UPDATED } from "@storybook/core-events";

export function HttpLoaderFactory(http: HttpClient) {
  return new TranslateHttpLoader(http, "../assets/i18n/", ".json")
}
import localePl from '@angular/common/locales/pt';
registerLocaleData(localePl);

@NgModule({
  declarations: [],
  imports: [
    HttpClientModule,
    CommonModule,
    TranslateModule.forRoot({
      loader: {
        provide: TranslateLoader,
        useFactory: HttpLoaderFactory,
        deps: [HttpClient]
      },
    }),
  ],
  exports: [TranslateModule],
  providers: [
    { provide: LOCALE_ID, useValue: 'pl'},
    TranslateService
  ]
})
export class StorybookTranslateModule {
  constructor(translate: TranslateService) {
    translate.setDefaultLang("pl");
    translate.use("pl");

    let channel = addons.getChannel();
    channel.addListener(GLOBALS_UPDATED, (args) => {
      translate.use(args.globals.locale)
    });
  }
}

This way you can use the local picker in the Toolbar and it will update the TranslateService's current language. The picker is specified in the preview file.

...
globalTypes: {
    locale: {
      description: 'Internationalization locale',
      defaultValue: 'en',
      toolbar: {
        icon: 'globe',
        items: [
          { value: 'en', right: '🇬🇧', title: 'English' },
          { value: 'pl', right: '🇵🇱', title: 'Polski' },
        ],
        dynamicTitle: true,
      },
    },
  },
...
Sameeksha7464 commented 1 week ago

what to do for standalone components?