Browse Source

feat(admin-ui): Enable selection of content language from list views

Relates to #883
Michael Bromley 4 years ago
parent
commit
eb9cb4fda6
19 changed files with 224 additions and 39 deletions
  1. 20 5
      packages/admin-ui/src/lib/catalog/src/components/collection-list/collection-list.component.html
  2. 0 1
      packages/admin-ui/src/lib/catalog/src/components/collection-list/collection-list.component.scss
  3. 15 1
      packages/admin-ui/src/lib/catalog/src/components/collection-list/collection-list.component.ts
  4. 7 0
      packages/admin-ui/src/lib/catalog/src/components/facet-list/facet-list.component.html
  5. 23 5
      packages/admin-ui/src/lib/catalog/src/components/facet-list/facet-list.component.ts
  6. 14 5
      packages/admin-ui/src/lib/catalog/src/components/product-list/product-list.component.html
  7. 26 2
      packages/admin-ui/src/lib/catalog/src/components/product-list/product-list.component.ts
  8. 7 8
      packages/admin-ui/src/lib/core/src/common/base-detail.component.ts
  9. 21 2
      packages/admin-ui/src/lib/core/src/common/generated-types.ts
  10. 2 0
      packages/admin-ui/src/lib/core/src/data/client-state/client-defaults.ts
  11. 17 0
      packages/admin-ui/src/lib/core/src/data/client-state/client-resolvers.ts
  12. 2 0
      packages/admin-ui/src/lib/core/src/data/client-state/client-types.graphql
  13. 8 0
      packages/admin-ui/src/lib/core/src/data/definitions/client-definitions.ts
  14. 11 0
      packages/admin-ui/src/lib/core/src/data/providers/client-data.service.ts
  15. 10 2
      packages/admin-ui/src/lib/core/src/data/providers/interceptor.ts
  16. 1 0
      packages/admin-ui/src/lib/core/src/providers/local-storage/local-storage.service.ts
  17. 12 1
      packages/admin-ui/src/lib/settings/src/components/shipping-method-list/shipping-method-list.component.html
  18. 20 7
      packages/admin-ui/src/lib/settings/src/components/shipping-method-list/shipping-method-list.component.ts
  19. 8 0
      packages/admin-ui/src/lib/static/styles/global/_utilities.scss

+ 20 - 5
packages/admin-ui/src/lib/catalog/src/components/collection-list/collection-list.component.html

@@ -1,13 +1,28 @@
 <vdr-action-bar>
     <vdr-ab-left>
-        <clr-checkbox-wrapper class="expand-all-toggle ml3">
-            <input type="checkbox" clrCheckbox [(ngModel)]="expandAll" />
-            <label>{{ 'catalog.expand-all-collections' | translate }}</label>
-        </clr-checkbox-wrapper>
+        <div class="flex center wrap">
+            <vdr-language-selector
+                class="mt2"
+                [availableLanguageCodes]="availableLanguages$ | async"
+                [currentLanguageCode]="contentLanguage$ | async"
+                (languageCodeChange)="setLanguage($event)"
+            ></vdr-language-selector>
+            <clr-checkbox-wrapper
+                class="expand-all-toggle ml3"
+                [ngClass]="(availableLanguages$ | async)?.length === 1 ? 'mt3' : 'mt1'"
+            >
+                <input type="checkbox" clrCheckbox [(ngModel)]="expandAll" />
+                <label>{{ 'catalog.expand-all-collections' | translate }}</label>
+            </clr-checkbox-wrapper>
+        </div>
     </vdr-ab-left>
     <vdr-ab-right>
         <vdr-action-bar-items locationId="collection-list"></vdr-action-bar-items>
-        <a class="btn btn-primary" *vdrIfPermissions="['CreateCatalog', 'CreateCollection']" [routerLink]="['./create']">
+        <a
+            class="btn btn-primary"
+            *vdrIfPermissions="['CreateCatalog', 'CreateCollection']"
+            [routerLink]="['./create']"
+        >
             <clr-icon shape="plus"></clr-icon>
             {{ 'catalog.create-new-collection' | translate }}
         </a>

+ 0 - 1
packages/admin-ui/src/lib/catalog/src/components/collection-list/collection-list.component.scss

@@ -7,7 +7,6 @@
 
 .expand-all-toggle {
     display: block;
-    margin-top: 14px;
 }
 
 .collection-wrapper {

+ 15 - 1
packages/admin-ui/src/lib/catalog/src/components/collection-list/collection-list.component.ts

@@ -4,12 +4,14 @@ import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
 import {
     DataService,
     GetCollectionList,
+    LanguageCode,
     ModalService,
     NotificationService,
     QueryResult,
+    ServerConfigService,
 } from '@vendure/admin-ui/core';
 import { combineLatest, EMPTY, Observable } from 'rxjs';
-import { distinctUntilChanged, map, shareReplay, switchMap, take } from 'rxjs/operators';
+import { distinctUntilChanged, map, shareReplay, switchMap, take, tap } from 'rxjs/operators';
 
 import { RearrangeEvent } from '../collection-tree/collection-tree.component';
 
@@ -23,6 +25,8 @@ export class CollectionListComponent implements OnInit, OnDestroy {
     activeCollectionId$: Observable<string | null>;
     activeCollectionTitle$: Observable<string>;
     items$: Observable<GetCollectionList.Items[]>;
+    availableLanguages$: Observable<LanguageCode[]>;
+    contentLanguage$: Observable<LanguageCode>;
     expandAll = false;
     private queryResult: QueryResult<any>;
 
@@ -32,6 +36,7 @@ export class CollectionListComponent implements OnInit, OnDestroy {
         private modalService: ModalService,
         private router: Router,
         private route: ActivatedRoute,
+        private serverConfigService: ServerConfigService,
     ) {}
 
     ngOnInit() {
@@ -51,6 +56,11 @@ export class CollectionListComponent implements OnInit, OnDestroy {
                 return '';
             }),
         );
+        this.availableLanguages$ = this.serverConfigService.getAvailableLanguages();
+        this.contentLanguage$ = this.dataService.client
+            .uiState()
+            .mapStream(({ uiState }) => uiState.contentLanguage)
+            .pipe(tap(() => this.refresh()));
     }
 
     ngOnDestroy() {
@@ -109,6 +119,10 @@ export class CollectionListComponent implements OnInit, OnDestroy {
         this.router.navigate(['./', params], { relativeTo: this.route, queryParamsHandling: 'preserve' });
     }
 
+    setLanguage(code: LanguageCode) {
+        this.dataService.client.setContentLanguage(code).subscribe();
+    }
+
     private refresh() {
         this.queryResult.ref.refetch();
     }

+ 7 - 0
packages/admin-ui/src/lib/catalog/src/components/facet-list/facet-list.component.html

@@ -1,4 +1,11 @@
 <vdr-action-bar>
+    <vdr-ab-left>
+        <vdr-language-selector
+            [availableLanguageCodes]="availableLanguages$ | async"
+            [currentLanguageCode]="contentLanguage$ | async"
+            (languageCodeChange)="setLanguage($event)"
+        ></vdr-language-selector>
+    </vdr-ab-left>
     <vdr-ab-right>
         <vdr-action-bar-items locationId="facet-list"></vdr-action-bar-items>
         <a class="btn btn-primary"

+ 23 - 5
packages/admin-ui/src/lib/catalog/src/components/facet-list/facet-list.component.ts

@@ -1,26 +1,31 @@
-import { Component } from '@angular/core';
+import { Component, OnInit } from '@angular/core';
 import { ActivatedRoute, Router } from '@angular/router';
 import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
-import { BaseListComponent } from '@vendure/admin-ui/core';
+import { BaseListComponent, LanguageCode, ServerConfigService } from '@vendure/admin-ui/core';
 import { DeletionResult, GetFacetList } from '@vendure/admin-ui/core';
 import { NotificationService } from '@vendure/admin-ui/core';
 import { DataService } from '@vendure/admin-ui/core';
 import { ModalService } from '@vendure/admin-ui/core';
-import { EMPTY } from 'rxjs';
-import { map, switchMap } from 'rxjs/operators';
+import { EMPTY, Observable } from 'rxjs';
+import { map, switchMap, tap } from 'rxjs/operators';
 
 @Component({
     selector: 'vdr-facet-list',
     templateUrl: './facet-list.component.html',
     styleUrls: ['./facet-list.component.scss'],
 })
-export class FacetListComponent extends BaseListComponent<GetFacetList.Query, GetFacetList.Items> {
+export class FacetListComponent
+    extends BaseListComponent<GetFacetList.Query, GetFacetList.Items>
+    implements OnInit {
+    availableLanguages$: Observable<LanguageCode[]>;
+    contentLanguage$: Observable<LanguageCode>;
     readonly initialLimit = 3;
     displayLimit: { [id: string]: number } = {};
     constructor(
         private dataService: DataService,
         private modalService: ModalService,
         private notificationService: NotificationService,
+        private serverConfigService: ServerConfigService,
         router: Router,
         route: ActivatedRoute,
     ) {
@@ -31,6 +36,15 @@ export class FacetListComponent extends BaseListComponent<GetFacetList.Query, Ge
         );
     }
 
+    ngOnInit() {
+        super.ngOnInit();
+        this.availableLanguages$ = this.serverConfigService.getAvailableLanguages();
+        this.contentLanguage$ = this.dataService.client
+            .uiState()
+            .mapStream(({ uiState }) => uiState.contentLanguage)
+            .pipe(tap(() => this.refresh()));
+    }
+
     toggleDisplayLimit(facet: GetFacetList.Items) {
         if (this.displayLimit[facet.id] === facet.values.length) {
             this.displayLimit[facet.id] = this.initialLimit;
@@ -69,6 +83,10 @@ export class FacetListComponent extends BaseListComponent<GetFacetList.Query, Ge
             );
     }
 
+    setLanguage(code: LanguageCode) {
+        this.dataService.client.setContentLanguage(code).subscribe();
+    }
+
     private showModalAndDelete(facetId: string, message?: string) {
         return this.modalService
             .dialog({

+ 14 - 5
packages/admin-ui/src/lib/catalog/src/components/product-list/product-list.component.html

@@ -23,10 +23,17 @@
                 </vdr-dropdown-menu>
             </vdr-dropdown>
         </div>
-        <clr-checkbox-wrapper>
-            <input type="checkbox" clrCheckbox [(ngModel)]="groupByProduct" />
-            <label>{{ 'catalog.group-by-product' | translate }}</label>
-        </clr-checkbox-wrapper>
+        <div class="flex wrap">
+            <clr-checkbox-wrapper class="mt2">
+                <input type="checkbox" clrCheckbox [(ngModel)]="groupByProduct"/>
+                <label>{{ 'catalog.group-by-product' | translate }}</label>
+            </clr-checkbox-wrapper>
+            <vdr-language-selector
+                [availableLanguageCodes]="availableLanguages$ | async"
+                [currentLanguageCode]="contentLanguage$ | async"
+                (languageCodeChange)="setLanguage($event)"
+            ></vdr-language-selector>
+        </div>
     </vdr-ab-left>
     <vdr-ab-right>
         <vdr-action-bar-items locationId="product-list"></vdr-action-bar-items>
@@ -58,7 +65,9 @@
                     [src]="asset | assetPreview:'tiny'"
                 />
                 <ng-template #imagePlaceholder>
-                    <div class="placeholder"><clr-icon shape="image" size="48"></clr-icon></div>
+                    <div class="placeholder">
+                        <clr-icon shape="image" size="48"></clr-icon>
+                    </div>
                 </ng-template>
             </div>
         </td>

+ 26 - 2
packages/admin-ui/src/lib/catalog/src/components/product-list/product-list.component.ts

@@ -6,14 +6,26 @@ import {
     DataService,
     JobQueueService,
     JobState,
+    LanguageCode,
     LogicalOperator,
     ModalService,
     NotificationService,
     SearchInput,
     SearchProducts,
+    ServerConfigService,
 } from '@vendure/admin-ui/core';
-import { EMPTY, Observable } from 'rxjs';
-import { delay, map, switchMap, take, takeUntil, withLatestFrom } from 'rxjs/operators';
+import { EMPTY, Observable, of } from 'rxjs';
+import {
+    delay,
+    distinctUntilChanged,
+    map,
+    shareReplay,
+    switchMap,
+    take,
+    takeUntil,
+    tap,
+    withLatestFrom,
+} from 'rxjs/operators';
 
 import { ProductSearchInputComponent } from '../product-search-input/product-search-input.component';
 
@@ -29,6 +41,8 @@ export class ProductListComponent
     facetValueIds: string[] = [];
     groupByProduct = true;
     facetValues$: Observable<SearchProducts.FacetValues[]>;
+    availableLanguages$: Observable<LanguageCode[]>;
+    contentLanguage$: Observable<LanguageCode>;
     @ViewChild('productSearchInputComponent', { static: true })
     private productSearchInput: ProductSearchInputComponent;
     constructor(
@@ -36,6 +50,7 @@ export class ProductListComponent
         private modalService: ModalService,
         private notificationService: NotificationService,
         private jobQueueService: JobQueueService,
+        private serverConfigService: ServerConfigService,
         router: Router,
         route: ActivatedRoute,
     ) {
@@ -80,6 +95,11 @@ export class ProductListComponent
         this.facetValues$.pipe(take(1), delay(100), withLatestFrom(fvids$)).subscribe(([__, ids]) => {
             this.productSearchInput.setFacetValues(ids);
         });
+        this.availableLanguages$ = this.serverConfigService.getAvailableLanguages();
+        this.contentLanguage$ = this.dataService.client
+            .uiState()
+            .mapStream(({ uiState }) => uiState.contentLanguage)
+            .pipe(tap(() => this.refresh()));
     }
 
     setSearchTerm(term: string) {
@@ -141,4 +161,8 @@ export class ProductListComponent
                 },
             );
     }
+
+    setLanguage(code: LanguageCode) {
+        this.dataService.client.setContentLanguage(code).subscribe();
+    }
 }

+ 7 - 8
packages/admin-ui/src/lib/core/src/common/base-detail.component.ts

@@ -29,23 +29,21 @@ export abstract class BaseDetailComponent<Entity extends { id: string; updatedAt
 
     init() {
         this.entity$ = this.route.data.pipe(
-            switchMap((data) => (data.entity as Observable<Entity>).pipe(takeUntil(this.destroy$))),
-            tap((entity) => (this.id = entity.id)),
+            switchMap(data => (data.entity as Observable<Entity>).pipe(takeUntil(this.destroy$))),
+            tap(entity => (this.id = entity.id)),
             shareReplay(1),
         );
         this.isNew$ = this.entity$.pipe(
-            map((entity) => entity.id === ''),
+            map(entity => entity.id === ''),
             shareReplay(1),
         );
         this.languageCode$ = this.route.paramMap.pipe(
-            map((paramMap) => paramMap.get('lang')),
-            switchMap((lang) => {
+            map(paramMap => paramMap.get('lang')),
+            switchMap(lang => {
                 if (lang) {
                     return of(lang as LanguageCode);
                 } else {
-                    return this.dataService.settings
-                        .getActiveChannel()
-                        .mapSingle((data) => data.activeChannel.defaultLanguageCode);
+                    return this.dataService.client.uiState().mapSingle(data => data.uiState.contentLanguage);
                 }
             }),
             distinctUntilChanged(),
@@ -69,6 +67,7 @@ export abstract class BaseDetailComponent<Entity extends { id: string; updatedAt
 
     setLanguage(code: LanguageCode) {
         this.setQueryParam('lang', code);
+        this.dataService.client.setContentLanguage(code).subscribe();
     }
 
     canDeactivate(): boolean {

+ 21 - 2
packages/admin-ui/src/lib/core/src/common/generated-types.ts

@@ -460,6 +460,7 @@ export type Mutation = {
   setActiveChannel: UserStatus;
   setAsLoggedIn: UserStatus;
   setAsLoggedOut: UserStatus;
+  setContentLanguage: LanguageCode;
   setOrderCustomFields?: Maybe<Order>;
   setUiLanguage: LanguageCode;
   setUiTheme: Scalars['String'];
@@ -906,6 +907,11 @@ export type MutationSetAsLoggedInArgs = {
 };
 
 
+export type MutationSetContentLanguageArgs = {
+  languageCode: LanguageCode;
+};
+
+
 export type MutationSetOrderCustomFieldsArgs = {
   input: UpdateOrderInput;
 };
@@ -5022,6 +5028,7 @@ export type UserStatus = {
 export type UiState = {
   __typename?: 'UiState';
   language: LanguageCode;
+  contentLanguage: LanguageCode;
   theme: Scalars['String'];
 };
 
@@ -5285,6 +5292,13 @@ export type SetUiLanguageMutationVariables = Exact<{
 
 export type SetUiLanguageMutation = Pick<Mutation, 'setUiLanguage'>;
 
+export type SetContentLanguageMutationVariables = Exact<{
+  languageCode: LanguageCode;
+}>;
+
+
+export type SetContentLanguageMutation = Pick<Mutation, 'setContentLanguage'>;
+
 export type SetUiThemeMutationVariables = Exact<{
   theme: Scalars['String'];
 }>;
@@ -5313,7 +5327,7 @@ export type GetUiStateQueryVariables = Exact<{ [key: string]: never; }>;
 
 export type GetUiStateQuery = { uiState: (
     { __typename?: 'UiState' }
-    & Pick<UiState, 'language' | 'theme'>
+    & Pick<UiState, 'language' | 'contentLanguage' | 'theme'>
   ) };
 
 export type GetClientStateQueryVariables = Exact<{ [key: string]: never; }>;
@@ -5327,7 +5341,7 @@ export type GetClientStateQuery = { networkStatus: (
     & UserStatusFragment
   ), uiState: (
     { __typename?: 'UiState' }
-    & Pick<UiState, 'language' | 'theme'>
+    & Pick<UiState, 'language' | 'contentLanguage' | 'theme'>
   ) };
 
 export type SetActiveChannelMutationVariables = Exact<{
@@ -8629,6 +8643,11 @@ export namespace SetUiLanguage {
   export type Mutation = SetUiLanguageMutation;
 }
 
+export namespace SetContentLanguage {
+  export type Variables = SetContentLanguageMutationVariables;
+  export type Mutation = SetContentLanguageMutation;
+}
+
 export namespace SetUiTheme {
   export type Variables = SetUiThemeMutationVariables;
   export type Mutation = SetUiThemeMutation;

+ 2 - 0
packages/admin-ui/src/lib/core/src/data/client-state/client-defaults.ts

@@ -5,6 +5,7 @@ import { LocalStorageService } from '../../providers/local-storage/local-storage
 
 export function getClientDefaults(localStorageService: LocalStorageService) {
     const currentLanguage = localStorageService.get('uiLanguageCode') || getDefaultUiLanguage();
+    const currentContentLanguage = localStorageService.get('contentLanguageCode') || getDefaultUiLanguage();
     const activeTheme = localStorageService.get('activeTheme') || 'default';
     return {
         networkStatus: {
@@ -22,6 +23,7 @@ export function getClientDefaults(localStorageService: LocalStorageService) {
         } as GetUserStatus.UserStatus,
         uiState: {
             language: currentLanguage,
+            contentLanguage: currentContentLanguage,
             theme: activeTheme,
             __typename: 'UiState',
         } as GetUiState.UiState,

+ 17 - 0
packages/admin-ui/src/lib/core/src/data/client-state/client-resolvers.ts

@@ -7,6 +7,7 @@ import {
     LanguageCode,
     SetActiveChannel,
     SetAsLoggedIn,
+    SetContentLanguage,
     SetUiLanguage,
     SetUiTheme,
     UpdateUserChannels,
@@ -76,6 +77,21 @@ export const clientResolvers: ResolverDefinition = {
                 uiState: {
                     __typename: 'UiState',
                     language: args.languageCode,
+                    contentLanguage: previous.uiState.contentLanguage,
+                    theme: previous.uiState.theme,
+                },
+            };
+            cache.writeQuery({ query: GET_UI_STATE, data });
+            return args.languageCode;
+        },
+        setContentLanguage: (_, args: SetContentLanguage.Variables, { cache }): LanguageCode => {
+            // tslint:disable-next-line:no-non-null-assertion
+            const previous = cache.readQuery<GetUiState.Query>({ query: GET_UI_STATE })!;
+            const data: GetUiState.Query = {
+                uiState: {
+                    __typename: 'UiState',
+                    language: previous.uiState.language,
+                    contentLanguage: args.languageCode,
                     theme: previous.uiState.theme,
                 },
             };
@@ -89,6 +105,7 @@ export const clientResolvers: ResolverDefinition = {
                 uiState: {
                     __typename: 'UiState',
                     language: previous.uiState.language,
+                    contentLanguage: previous.uiState.contentLanguage,
                     theme: args.theme,
                 },
             };

+ 2 - 0
packages/admin-ui/src/lib/core/src/data/client-state/client-types.graphql

@@ -10,6 +10,7 @@ type Mutation {
     setAsLoggedIn(input: UserStatusInput!): UserStatus!
     setAsLoggedOut: UserStatus!
     setUiLanguage(languageCode: LanguageCode!): LanguageCode!
+    setContentLanguage(languageCode: LanguageCode!): LanguageCode!
     setUiTheme(theme: String!): String!
     setActiveChannel(channelId: ID!): UserStatus!
     updateUserChannels(channels: [CurrentUserChannelInput!]!): UserStatus!
@@ -30,6 +31,7 @@ type UserStatus {
 
 type UiState {
     language: LanguageCode!
+    contentLanguage: LanguageCode!
     theme: String!
 }
 

+ 8 - 0
packages/admin-ui/src/lib/core/src/data/definitions/client-definitions.ts

@@ -52,6 +52,12 @@ export const SET_UI_LANGUAGE = gql`
     }
 `;
 
+export const SET_CONTENT_LANGUAGE = gql`
+    mutation SetContentLanguage($languageCode: LanguageCode!) {
+        setContentLanguage(languageCode: $languageCode) @client
+    }
+`;
+
 export const SET_UI_THEME = gql`
     mutation SetUiTheme($theme: String!) {
         setUiTheme(theme: $theme) @client
@@ -79,6 +85,7 @@ export const GET_UI_STATE = gql`
     query GetUiState {
         uiState @client {
             language
+            contentLanguage
             theme
         }
     }
@@ -94,6 +101,7 @@ export const GET_CLIENT_STATE = gql`
         }
         uiState @client {
             language
+            contentLanguage
             theme
         }
     }

+ 11 - 0
packages/admin-ui/src/lib/core/src/data/providers/client-data.service.ts

@@ -9,6 +9,7 @@ import {
     RequestStarted,
     SetActiveChannel,
     SetAsLoggedIn,
+    SetContentLanguage,
     SetUiLanguage,
     SetUiTheme,
     UpdateUserChannels,
@@ -22,6 +23,7 @@ import {
     SET_ACTIVE_CHANNEL,
     SET_AS_LOGGED_IN,
     SET_AS_LOGGED_OUT,
+    SET_CONTENT_LANGUAGE,
     SET_UI_LANGUAGE,
     SET_UI_THEME,
     UPDATE_USER_CHANNELS,
@@ -80,6 +82,15 @@ export class ClientDataService {
         });
     }
 
+    setContentLanguage(languageCode: LanguageCode) {
+        return this.baseDataService.mutate<SetContentLanguage.Mutation, SetContentLanguage.Variables>(
+            SET_CONTENT_LANGUAGE,
+            {
+                languageCode,
+            },
+        );
+    }
+
     setUiTheme(theme: string) {
         return this.baseDataService.mutate<SetUiTheme.Mutation, SetUiTheme.Variables>(SET_UI_THEME, {
             theme,

+ 10 - 2
packages/admin-ui/src/lib/core/src/data/providers/interceptor.ts

@@ -12,7 +12,7 @@ import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
 import { DEFAULT_AUTH_TOKEN_HEADER_KEY } from '@vendure/common/lib/shared-constants';
 import { AdminUiConfig } from '@vendure/common/lib/shared-types';
 import { Observable } from 'rxjs';
-import { tap } from 'rxjs/operators';
+import { switchMap, tap } from 'rxjs/operators';
 
 import { getAppConfig } from '../../app.config';
 import { AuthService } from '../../providers/auth/auth.service';
@@ -44,7 +44,15 @@ export class DefaultInterceptor implements HttpInterceptor {
 
     intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
         this.dataService.client.startRequest().subscribe();
-        return next.handle(req).pipe(
+        return this.dataService.client.uiState().single$.pipe(
+            switchMap(({ uiState }) => {
+                const request = req.clone({
+                    setParams: {
+                        languageCode: uiState?.contentLanguage ?? '',
+                    },
+                });
+                return next.handle(request);
+            }),
             tap(
                 event => {
                     if (event instanceof HttpResponse) {

+ 1 - 0
packages/admin-ui/src/lib/core/src/providers/local-storage/local-storage.service.ts

@@ -8,6 +8,7 @@ export type LocalStorageTypeMap = {
     activeChannelToken: string;
     authToken: string;
     uiLanguageCode: LanguageCode;
+    contentLanguageCode: LanguageCode;
     orderListLastCustomFilters: any;
     dashboardWidgetLayout: WidgetLayoutDefinition;
     activeTheme: string;

+ 12 - 1
packages/admin-ui/src/lib/settings/src/components/shipping-method-list/shipping-method-list.component.html

@@ -1,7 +1,18 @@
 <vdr-action-bar>
+    <vdr-ab-left>
+        <vdr-language-selector
+            [availableLanguageCodes]="availableLanguages$ | async"
+            [currentLanguageCode]="contentLanguage$ | async"
+            (languageCodeChange)="setLanguage($event)"
+        ></vdr-language-selector>
+    </vdr-ab-left>
     <vdr-ab-right>
         <vdr-action-bar-items locationId="shipping-method-list"></vdr-action-bar-items>
-        <a class="btn btn-primary" [routerLink]="['./create']" *vdrIfPermissions="['CreateSettings', 'CreateShippingMethod']">
+        <a
+            class="btn btn-primary"
+            [routerLink]="['./create']"
+            *vdrIfPermissions="['CreateSettings', 'CreateShippingMethod']"
+        >
             <clr-icon shape="plus"></clr-icon>
             {{ 'settings.create-new-shipping-method' | translate }}
         </a>

+ 20 - 7
packages/admin-ui/src/lib/settings/src/components/shipping-method-list/shipping-method-list.component.ts

@@ -1,19 +1,20 @@
 import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';
 import { ActivatedRoute, Router } from '@angular/router';
 import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
-import { BaseListComponent } from '@vendure/admin-ui/core';
 import {
+    BaseListComponent,
+    DataService,
     GetActiveChannel,
     GetShippingMethodList,
+    LanguageCode,
+    ModalService,
+    NotificationService,
+    ServerConfigService,
     ShippingMethodQuote,
     TestEligibleShippingMethodsInput,
-    TestShippingMethodInput,
 } from '@vendure/admin-ui/core';
-import { NotificationService } from '@vendure/admin-ui/core';
-import { DataService } from '@vendure/admin-ui/core';
-import { ModalService } from '@vendure/admin-ui/core';
-import { EMPTY, Observable, of, Subject } from 'rxjs';
-import { switchMap } from 'rxjs/operators';
+import { EMPTY, Observable, Subject } from 'rxjs';
+import { switchMap, tap } from 'rxjs/operators';
 
 import { TestAddress } from '../test-address-form/test-address-form.component';
 import { TestOrderLine } from '../test-order-builder/test-order-builder.component';
@@ -32,12 +33,15 @@ export class ShippingMethodListComponent
     testOrderLines: TestOrderLine[];
     testDataUpdated = false;
     testResult$: Observable<ShippingMethodQuote[] | undefined>;
+    availableLanguages$: Observable<LanguageCode[]>;
+    contentLanguage$: Observable<LanguageCode>;
     private fetchTestResult$ = new Subject<[TestAddress, TestOrderLine[]]>();
 
     constructor(
         private modalService: ModalService,
         private notificationService: NotificationService,
         private dataService: DataService,
+        private serverConfigService: ServerConfigService,
         router: Router,
         route: ActivatedRoute,
     ) {
@@ -65,6 +69,11 @@ export class ShippingMethodListComponent
         this.activeChannel$ = this.dataService.settings
             .getActiveChannel()
             .mapStream(data => data.activeChannel);
+        this.availableLanguages$ = this.serverConfigService.getAvailableLanguages();
+        this.contentLanguage$ = this.dataService.client
+            .uiState()
+            .mapStream(({ uiState }) => uiState.contentLanguage)
+            .pipe(tap(() => this.refresh()));
     }
 
     deleteShippingMethod(id: string) {
@@ -114,4 +123,8 @@ export class ShippingMethodListComponent
         this.fetchTestResult$.next([this.testAddress, this.testOrderLines]);
         this.testDataUpdated = false;
     }
+
+    setLanguage(code: LanguageCode) {
+        this.dataService.client.setContentLanguage(code).subscribe();
+    }
 }

+ 8 - 0
packages/admin-ui/src/lib/static/styles/global/_utilities.scss

@@ -11,6 +11,14 @@ $space-4: $space-unit * 3;
     display: flex;
 }
 
+.flex.center {
+    align-items: center;
+}
+
+.flex.wrap {
+    flex-wrap: wrap;
+}
+
 .flex-spacer {
     flex: 1;
 }