Browse Source

feat(admin-ui): Dates respect UI language setting

Relates to #568
Michael Bromley 5 years ago
parent
commit
dd0e73abf6
15 changed files with 142 additions and 15 deletions
  1. 1 1
      packages/admin-ui/src/lib/core/src/shared/components/datetime-picker/datetime-picker.component.html
  2. 2 2
      packages/admin-ui/src/lib/core/src/shared/components/entity-info/entity-info.component.html
  3. 1 1
      packages/admin-ui/src/lib/core/src/shared/components/timeline-entry/timeline-entry.component.html
  4. 33 0
      packages/admin-ui/src/lib/core/src/shared/pipes/locale-date.pipe.spec.ts
  5. 92 0
      packages/admin-ui/src/lib/core/src/shared/pipes/locale-date.pipe.ts
  6. 2 0
      packages/admin-ui/src/lib/core/src/shared/shared.module.ts
  7. 2 2
      packages/admin-ui/src/lib/customer/src/components/customer-detail/customer-detail.component.html
  8. 1 1
      packages/admin-ui/src/lib/dashboard/src/widgets/order-summary-widget/order-summary-widget.component.html
  9. 2 2
      packages/admin-ui/src/lib/marketing/src/components/promotion-list/promotion-list.component.html
  10. 1 1
      packages/admin-ui/src/lib/order/src/components/fulfillment-detail/fulfillment-detail.component.html
  11. 1 1
      packages/admin-ui/src/lib/order/src/components/line-fulfillment/line-fulfillment.component.html
  12. 1 1
      packages/admin-ui/src/lib/order/src/components/order-list/order-list.component.html
  13. 1 1
      packages/admin-ui/src/lib/order/src/components/order-payment-card/order-payment-card.component.html
  14. 1 1
      packages/admin-ui/src/lib/order/src/components/order-table/order-table.component.html
  15. 1 1
      packages/admin-ui/src/lib/system/src/components/health-check/health-check.component.html

+ 1 - 1
packages/admin-ui/src/lib/core/src/shared/components/datetime-picker/datetime-picker.component.html

@@ -1,7 +1,7 @@
 <div class="input-wrapper">
     <input
         readonly
-        [ngModel]="selected$ | async | date: 'medium'"
+        [ngModel]="selected$ | async | localeDate: 'medium'"
         class="selected-datetime"
         (keydown.enter)="dropdownComponent.toggleOpen()"
         (keydown.space)="dropdownComponent.toggleOpen()"

+ 2 - 2
packages/admin-ui/src/lib/core/src/shared/components/entity-info/entity-info.component.html

@@ -8,10 +8,10 @@
                 {{ entity.id }}
             </vdr-labeled-data>
             <vdr-labeled-data *ngIf="entity.createdAt" [label]="'common.created-at' | translate">
-                {{ entity.createdAt | date: 'medium' }}
+                {{ entity.createdAt | localeDate: 'medium' }}
             </vdr-labeled-data>
             <vdr-labeled-data *ngIf="entity.updatedAt" [label]="'common.updated-at' | translate">
-                {{ entity.updatedAt | date: 'medium' }}
+                {{ entity.updatedAt | localeDate: 'medium' }}
             </vdr-labeled-data>
         </div>
     </vdr-dropdown-menu>

+ 1 - 1
packages/admin-ui/src/lib/core/src/shared/components/timeline-entry/timeline-entry.component.html

@@ -18,7 +18,7 @@
     <div class="entry-body">
         <div class="detail">
             <div class="time">
-                {{ createdAt | date: 'short' }}
+                {{ createdAt | localeDate: 'short' }}
             </div>
             <div class="name">
                 {{ name }}

+ 33 - 0
packages/admin-ui/src/lib/core/src/shared/pipes/locale-date.pipe.spec.ts

@@ -0,0 +1,33 @@
+import { LanguageCode } from '../../common/generated-types';
+
+import { LocaleDatePipe } from './locale-date.pipe';
+
+describe('LocaleDatePipe', () => {
+    const testDate = new Date('2021-01-12T09:12:42');
+    it('medium format', () => {
+        const pipe = new LocaleDatePipe();
+        expect(pipe.transform(testDate, 'medium', LanguageCode.en)).toBe('Jan 12, 2021, 9:12:42 AM');
+    });
+    it('mediumTime format', () => {
+        const pipe = new LocaleDatePipe();
+        expect(pipe.transform(testDate, 'mediumTime', LanguageCode.en)).toBe('9:12:42 AM');
+    });
+    it('short format', () => {
+        const pipe = new LocaleDatePipe();
+        expect(pipe.transform(testDate, 'short', LanguageCode.en)).toBe('1/12/21, 9:12 AM');
+    });
+    it('longDate format', () => {
+        const pipe = new LocaleDatePipe();
+        expect(pipe.transform(testDate, 'longDate', LanguageCode.en)).toBe('January 12, 2021');
+    });
+
+    it('medium format German', () => {
+        const pipe = new LocaleDatePipe();
+        expect(pipe.transform(testDate, 'medium', LanguageCode.de)).toBe('12. Jan. 2021, 9:12:42 AM');
+    });
+
+    it('medium format Chinese', () => {
+        const pipe = new LocaleDatePipe();
+        expect(pipe.transform(testDate, 'medium', LanguageCode.zh)).toBe('2021年1月12日 上午9:12:42');
+    });
+});

+ 92 - 0
packages/admin-ui/src/lib/core/src/shared/pipes/locale-date.pipe.ts

@@ -0,0 +1,92 @@
+import { ChangeDetectorRef, OnDestroy, Optional, Pipe, PipeTransform } from '@angular/core';
+import { DataService } from '@vendure/admin-ui/core';
+import { Subscription } from 'rxjs';
+
+import { LanguageCode } from '../../common/generated-types';
+
+/**
+ * @description
+ * A replacement of the Angular DatePipe which makes use of the Intl API
+ * to format dates according to the selected UI language.
+ */
+@Pipe({
+    name: 'localeDate',
+    pure: false,
+})
+export class LocaleDatePipe implements PipeTransform, OnDestroy {
+    private locale: string;
+    private readonly subscription: Subscription;
+
+    constructor(
+        @Optional() private dataService?: DataService,
+        @Optional() changeDetectorRef?: ChangeDetectorRef,
+    ) {
+        if (this.dataService && changeDetectorRef) {
+            this.subscription = this.dataService.client
+                .uiState()
+                .mapStream(data => data.uiState.language)
+                .subscribe(languageCode => {
+                    this.locale = languageCode.replace(/_/g, '-');
+                    changeDetectorRef.markForCheck();
+                });
+        }
+    }
+
+    ngOnDestroy() {
+        if (this.subscription) {
+            this.subscription.unsubscribe();
+        }
+    }
+
+    transform(value: unknown, ...args: unknown[]): unknown {
+        const [format, locale] = args;
+        if (this.locale || typeof locale === 'string') {
+            const activeLocale = typeof locale === 'string' ? locale : this.locale;
+            const date =
+                value instanceof Date ? value : typeof value === 'string' ? new Date(value) : undefined;
+            if (date) {
+                const options = this.getOptionsForFormat(typeof format === 'string' ? format : 'medium');
+                return new Intl.DateTimeFormat(activeLocale, options).format(date);
+            }
+        }
+    }
+
+    private getOptionsForFormat(dateFormat: string): Intl.DateTimeFormatOptions | undefined {
+        switch (dateFormat) {
+            case 'medium':
+                return {
+                    month: 'short',
+                    year: 'numeric',
+                    day: 'numeric',
+                    hour: 'numeric',
+                    minute: 'numeric',
+                    second: 'numeric',
+                    hour12: true,
+                };
+            case 'mediumTime':
+                return {
+                    hour: 'numeric',
+                    minute: 'numeric',
+                    second: 'numeric',
+                    hour12: true,
+                };
+            case 'longDate':
+                return {
+                    year: 'numeric',
+                    month: 'long',
+                    day: 'numeric',
+                };
+            case 'short':
+                return {
+                    day: 'numeric',
+                    month: 'numeric',
+                    year: '2-digit',
+                    hour: 'numeric',
+                    minute: 'numeric',
+                    hour12: true,
+                };
+            default:
+                return;
+        }
+    }
+}

+ 2 - 0
packages/admin-ui/src/lib/core/src/shared/shared.module.ts

@@ -96,6 +96,7 @@ import { CustomFieldLabelPipe } from './pipes/custom-field-label.pipe';
 import { DurationPipe } from './pipes/duration.pipe';
 import { FileSizePipe } from './pipes/file-size.pipe';
 import { HasPermissionPipe } from './pipes/has-permission.pipe';
+import { LocaleDatePipe } from './pipes/locale-date.pipe';
 import { SentenceCasePipe } from './pipes/sentence-case.pipe';
 import { SortPipe } from './pipes/sort.pipe';
 import { StateI18nTokenPipe } from './pipes/state-i18n-token.pipe';
@@ -193,6 +194,7 @@ const DECLARATIONS = [
     HelpTooltipComponent,
     CustomerGroupFormInputComponent,
     AddressFormComponent,
+    LocaleDatePipe,
 ];
 
 const DYNAMIC_FORM_INPUTS = [

+ 2 - 2
packages/admin-ui/src/lib/customer/src/components/customer-detail/customer-detail.component.html

@@ -3,7 +3,7 @@
         <div class="flex clr-align-items-center">
             <vdr-entity-info [entity]="entity$ | async"></vdr-entity-info>
             <vdr-customer-status-label [customer]="entity$ | async"></vdr-customer-status-label>
-            <div class="last-login" *ngIf="(entity$ | async)?.user?.lastLogin as lastLogin" [title]="lastLogin | date:'medium'">
+            <div class="last-login" *ngIf="(entity$ | async)?.user?.lastLogin as lastLogin" [title]="lastLogin | localeDate:'medium'">
                 {{ 'customer.last-login' | translate }}: {{ lastLogin | timeAgo }}
             </div>
         </div>
@@ -140,7 +140,7 @@
                 <td class="left">{{ order.code }}</td>
                 <td class="left">{{ order.state }}</td>
                 <td class="left">{{ order.total / 100 | currency: order.currencyCode }}</td>
-                <td class="left">{{ order.updatedAt | date: 'medium' }}</td>
+                <td class="left">{{ order.updatedAt | localeDate: 'medium' }}</td>
                 <td class="right">
                     <vdr-table-row-action
                         iconShape="shopping-cart"

+ 1 - 1
packages/admin-ui/src/lib/dashboard/src/widgets/order-summary-widget/order-summary-widget.component.html

@@ -27,6 +27,6 @@
     </div>
 
     <div class="date-range p5" *ngIf="dateRange$ | async as range">
-        {{ range.start | date }} - {{ range.end | date }}
+        {{ range.start | localeDate }} - {{ range.end | localeDate }}
     </div>
 </div>

+ 2 - 2
packages/admin-ui/src/lib/marketing/src/components/promotion-list/promotion-list.component.html

@@ -32,8 +32,8 @@
                 {{ promotion.couponCode }}
             </vdr-chip>
         </td>
-        <td class="left align-middle">{{ promotion.startsAt | date: 'longDate' }}</td>
-        <td class="left align-middle">{{ promotion.endsAt | date: 'longDate' }}</td>
+        <td class="left align-middle">{{ promotion.startsAt | localeDate: 'longDate' }}</td>
+        <td class="left align-middle">{{ promotion.endsAt | localeDate: 'longDate' }}</td>
         <td class="align-middle">
             <vdr-chip *ngIf="!promotion.enabled">{{ 'common.disabled' | translate }}</vdr-chip>
         </td>

+ 1 - 1
packages/admin-ui/src/lib/order/src/components/fulfillment-detail/fulfillment-detail.component.html

@@ -1,5 +1,5 @@
 <vdr-labeled-data [label]="'common.created-at' | translate">
-    {{ fulfillment?.createdAt | date: 'medium' }}
+    {{ fulfillment?.createdAt | localeDate: 'medium' }}
 </vdr-labeled-data>
 <vdr-labeled-data [label]="'order.fulfillment-method' | translate">
     {{ fulfillment?.method }}

+ 1 - 1
packages/admin-ui/src/lib/order/src/components/line-fulfillment/line-fulfillment.component.html

@@ -31,7 +31,7 @@
                 }})
             </div>
             <vdr-labeled-data [label]="'common.created-at' | translate">
-                {{ item.fulfillment.createdAt | date: 'medium' }}
+                {{ item.fulfillment.createdAt | localeDate: 'medium' }}
             </vdr-labeled-data>
             <vdr-labeled-data [label]="'order.fulfillment-method' | translate">
                 {{ item.fulfillment.method }}

+ 1 - 1
packages/admin-ui/src/lib/order/src/components/order-list/order-list.component.html

@@ -100,7 +100,7 @@
         </td>
         <td class="left align-middle">{{ order.total / 100 | currency: order.currencyCode }}</td>
         <td class="left align-middle">{{ order.updatedAt | timeAgo }}</td>
-        <td class="left align-middle">{{ order.orderPlacedAt | date: 'medium' }}</td>
+        <td class="left align-middle">{{ order.orderPlacedAt | localeDate: 'medium' }}</td>
         <td class="left align-middle">{{ getShippingNames(order) }}</td>
         <td class="right align-middle">
             <vdr-table-row-action

+ 1 - 1
packages/admin-ui/src/lib/order/src/components/order-payment-card/order-payment-card.component.html

@@ -17,7 +17,7 @@
         </div>
         <div class="card-block">
             <vdr-labeled-data [label]="'common.created-at' | translate">
-                {{ refund.createdAt | date: 'medium' }}
+                {{ refund.createdAt | localeDate: 'medium' }}
             </vdr-labeled-data>
             <vdr-labeled-data [label]="'order.refund-total' | translate">
                 {{ refund.total / 100 | currency: currencyCode }}

+ 1 - 1
packages/admin-ui/src/lib/order/src/components/order-table/order-table.component.html

@@ -54,7 +54,7 @@
                     <ng-container [ngSwitch]="customField.type">
                         <ng-template [ngSwitchCase]="'datetime'">
                             <span [title]="line.customFields[customField.name]">{{
-                                line.customFields[customField.name] | date: 'short'
+                                line.customFields[customField.name] | localeDate: 'short'
                             }}</span>
                         </ng-template>
                         <ng-template [ngSwitchCase]="'boolean'">

+ 1 - 1
packages/admin-ui/src/lib/system/src/components/health-check/health-check.component.html

@@ -17,7 +17,7 @@
                 </ng-template>
                 <div class="last-checked">
                     {{ 'system.health-last-checked' | translate }}:
-                    {{ healthCheckService.lastCheck$ | async | date: 'mediumTime' }}
+                    {{ healthCheckService.lastCheck$ | async | localeDate: 'mediumTime' }}
                 </div>
             </div>
         </div>