diff options
Diffstat (limited to 'client')
22 files changed, 230 insertions, 142 deletions
diff --git a/client/angular.json b/client/angular.json index d929248d4..9b069422f 100644 --- a/client/angular.json +++ b/client/angular.json | |||
@@ -199,7 +199,11 @@ | |||
199 | "is-plain-object", | 199 | "is-plain-object", |
200 | "parse-srcset", | 200 | "parse-srcset", |
201 | "deepmerge", | 201 | "deepmerge", |
202 | "core-js/features/reflect" | 202 | "core-js/features/reflect", |
203 | "@formatjs/intl-locale/polyfill", | ||
204 | "@formatjs/intl-locale/should-polyfill", | ||
205 | "@formatjs/intl-pluralrules/polyfill-force", | ||
206 | "@formatjs/intl-pluralrules/should-polyfill" | ||
203 | ], | 207 | ], |
204 | "scripts": [], | 208 | "scripts": [], |
205 | "vendorChunk": true, | 209 | "vendorChunk": true, |
diff --git a/client/package.json b/client/package.json index 202a0f836..564e56ae7 100644 --- a/client/package.json +++ b/client/package.json | |||
@@ -47,6 +47,8 @@ | |||
47 | "@angular/service-worker": "^16.0.2", | 47 | "@angular/service-worker": "^16.0.2", |
48 | "@babel/core": "^7.18.5", | 48 | "@babel/core": "^7.18.5", |
49 | "@babel/preset-env": "^7.18.2", | 49 | "@babel/preset-env": "^7.18.2", |
50 | "@formatjs/intl-locale": "^3.3.1", | ||
51 | "@formatjs/intl-pluralrules": "^5.2.2", | ||
50 | "@ng-bootstrap/ng-bootstrap": "^14.0.1", | 52 | "@ng-bootstrap/ng-bootstrap": "^14.0.1", |
51 | "@ng-select/ng-select": "^10.0.3", | 53 | "@ng-select/ng-select": "^10.0.3", |
52 | "@ngx-loading-bar/core": "^6.0.0", | 54 | "@ngx-loading-bar/core": "^6.0.0", |
diff --git a/client/src/app/+admin/config/edit-custom-config/edit-configuration.service.ts b/client/src/app/+admin/config/edit-custom-config/edit-configuration.service.ts index 628c2d102..42c0e6dc2 100644 --- a/client/src/app/+admin/config/edit-custom-config/edit-configuration.service.ts +++ b/client/src/app/+admin/config/edit-custom-config/edit-configuration.service.ts | |||
@@ -1,6 +1,6 @@ | |||
1 | import { Injectable } from '@angular/core' | 1 | import { Injectable } from '@angular/core' |
2 | import { FormGroup } from '@angular/forms' | 2 | import { FormGroup } from '@angular/forms' |
3 | import { prepareIcu } from '@app/helpers' | 3 | import { formatICU } from '@app/helpers' |
4 | 4 | ||
5 | export type ResolutionOption = { | 5 | export type ResolutionOption = { |
6 | id: string | 6 | id: string |
@@ -99,10 +99,7 @@ export class EditConfigurationService { | |||
99 | return { | 99 | return { |
100 | value, | 100 | value, |
101 | atMost: noneOnAuto, // auto switches everything to a least estimation since ffmpeg will take as many threads as possible | 101 | atMost: noneOnAuto, // auto switches everything to a least estimation since ffmpeg will take as many threads as possible |
102 | unit: prepareIcu($localize`{value, plural, =1 {thread} other {threads}}`)( | 102 | unit: formatICU($localize`{value, plural, =1 {thread} other {threads}}`, { value }) |
103 | { value }, | ||
104 | $localize`threads` | ||
105 | ) | ||
106 | } | 103 | } |
107 | } | 104 | } |
108 | } | 105 | } |
diff --git a/client/src/app/+admin/follows/followers-list/followers-list.component.ts b/client/src/app/+admin/follows/followers-list/followers-list.component.ts index cebb2e1a2..618892242 100644 --- a/client/src/app/+admin/follows/followers-list/followers-list.component.ts +++ b/client/src/app/+admin/follows/followers-list/followers-list.component.ts | |||
@@ -1,7 +1,7 @@ | |||
1 | import { SortMeta } from 'primeng/api' | 1 | import { SortMeta } from 'primeng/api' |
2 | import { Component, OnInit } from '@angular/core' | 2 | import { Component, OnInit } from '@angular/core' |
3 | import { ConfirmService, Notifier, RestPagination, RestTable } from '@app/core' | 3 | import { ConfirmService, Notifier, RestPagination, RestTable } from '@app/core' |
4 | import { prepareIcu } from '@app/helpers' | 4 | import { formatICU } from '@app/helpers' |
5 | import { AdvancedInputFilter } from '@app/shared/shared-forms' | 5 | import { AdvancedInputFilter } from '@app/shared/shared-forms' |
6 | import { InstanceFollowService } from '@app/shared/shared-instance' | 6 | import { InstanceFollowService } from '@app/shared/shared-instance' |
7 | import { DropdownAction } from '@app/shared/shared-main' | 7 | import { DropdownAction } from '@app/shared/shared-main' |
@@ -63,9 +63,9 @@ export class FollowersListComponent extends RestTable <ActorFollow> implements O | |||
63 | .subscribe({ | 63 | .subscribe({ |
64 | next: () => { | 64 | next: () => { |
65 | // eslint-disable-next-line max-len | 65 | // eslint-disable-next-line max-len |
66 | const message = prepareIcu($localize`Accepted {count, plural, =1 {{followerName} follow request} other {{count} follow requests}}`)( | 66 | const message = formatICU( |
67 | { count: follows.length, followerName: this.buildFollowerName(follows[0]) }, | 67 | $localize`Accepted {count, plural, =1 {{followerName} follow request} other {{count} follow requests}}`, |
68 | $localize`Follow requests accepted` | 68 | { count: follows.length, followerName: this.buildFollowerName(follows[0]) } |
69 | ) | 69 | ) |
70 | this.notifier.success(message) | 70 | this.notifier.success(message) |
71 | 71 | ||
@@ -78,9 +78,9 @@ export class FollowersListComponent extends RestTable <ActorFollow> implements O | |||
78 | 78 | ||
79 | async rejectFollower (follows: ActorFollow[]) { | 79 | async rejectFollower (follows: ActorFollow[]) { |
80 | // eslint-disable-next-line max-len | 80 | // eslint-disable-next-line max-len |
81 | const message = prepareIcu($localize`Do you really want to reject {count, plural, =1 {{followerName} follow request?} other {{count} follow requests?}}`)( | 81 | const message = formatICU( |
82 | { count: follows.length, followerName: this.buildFollowerName(follows[0]) }, | 82 | $localize`Do you really want to reject {count, plural, =1 {{followerName} follow request?} other {{count} follow requests?}}`, |
83 | $localize`Do you really want to reject these follow requests?` | 83 | { count: follows.length, followerName: this.buildFollowerName(follows[0]) } |
84 | ) | 84 | ) |
85 | 85 | ||
86 | const res = await this.confirmService.confirm(message, $localize`Reject`) | 86 | const res = await this.confirmService.confirm(message, $localize`Reject`) |
@@ -90,9 +90,9 @@ export class FollowersListComponent extends RestTable <ActorFollow> implements O | |||
90 | .subscribe({ | 90 | .subscribe({ |
91 | next: () => { | 91 | next: () => { |
92 | // eslint-disable-next-line max-len | 92 | // eslint-disable-next-line max-len |
93 | const message = prepareIcu($localize`Rejected {count, plural, =1 {{followerName} follow request} other {{count} follow requests}}`)( | 93 | const message = formatICU( |
94 | { count: follows.length, followerName: this.buildFollowerName(follows[0]) }, | 94 | $localize`Rejected {count, plural, =1 {{followerName} follow request} other {{count} follow requests}}`, |
95 | $localize`Follow requests rejected` | 95 | { count: follows.length, followerName: this.buildFollowerName(follows[0]) } |
96 | ) | 96 | ) |
97 | this.notifier.success(message) | 97 | this.notifier.success(message) |
98 | 98 | ||
@@ -110,9 +110,9 @@ export class FollowersListComponent extends RestTable <ActorFollow> implements O | |||
110 | message += '<br /><br />' | 110 | message += '<br /><br />' |
111 | 111 | ||
112 | // eslint-disable-next-line max-len | 112 | // eslint-disable-next-line max-len |
113 | message += prepareIcu($localize`Do you really want to delete {count, plural, =1 {{followerName} follow request?} other {{count} follow requests?}}`)( | 113 | message += formatICU( |
114 | icuParams, | 114 | $localize`Do you really want to delete {count, plural, =1 {{followerName} follow request?} other {{count} follow requests?}}`, |
115 | $localize`Do you really want to delete these follow requests?` | 115 | icuParams |
116 | ) | 116 | ) |
117 | 117 | ||
118 | const res = await this.confirmService.confirm(message, $localize`Delete`) | 118 | const res = await this.confirmService.confirm(message, $localize`Delete`) |
@@ -122,9 +122,9 @@ export class FollowersListComponent extends RestTable <ActorFollow> implements O | |||
122 | .subscribe({ | 122 | .subscribe({ |
123 | next: () => { | 123 | next: () => { |
124 | // eslint-disable-next-line max-len | 124 | // eslint-disable-next-line max-len |
125 | const message = prepareIcu($localize`Removed {count, plural, =1 {{followerName} follow request} other {{count} follow requests}}`)( | 125 | const message = formatICU( |
126 | icuParams, | 126 | $localize`Removed {count, plural, =1 {{followerName} follow request} other {{count} follow requests}}`, |
127 | $localize`Follow requests removed` | 127 | icuParams |
128 | ) | 128 | ) |
129 | 129 | ||
130 | this.notifier.success(message) | 130 | this.notifier.success(message) |
diff --git a/client/src/app/+admin/follows/following-list/follow-modal.component.ts b/client/src/app/+admin/follows/following-list/follow-modal.component.ts index 8f74e82a6..54b3cebc5 100644 --- a/client/src/app/+admin/follows/following-list/follow-modal.component.ts +++ b/client/src/app/+admin/follows/following-list/follow-modal.component.ts | |||
@@ -1,6 +1,6 @@ | |||
1 | import { Component, EventEmitter, OnInit, Output, ViewChild } from '@angular/core' | 1 | import { Component, EventEmitter, OnInit, Output, ViewChild } from '@angular/core' |
2 | import { Notifier } from '@app/core' | 2 | import { Notifier } from '@app/core' |
3 | import { prepareIcu } from '@app/helpers' | 3 | import { formatICU } from '@app/helpers' |
4 | import { splitAndGetNotEmpty, UNIQUE_HOSTS_OR_HANDLE_VALIDATOR } from '@app/shared/form-validators/host-validators' | 4 | import { splitAndGetNotEmpty, UNIQUE_HOSTS_OR_HANDLE_VALIDATOR } from '@app/shared/form-validators/host-validators' |
5 | import { FormReactive, FormReactiveService } from '@app/shared/shared-forms' | 5 | import { FormReactive, FormReactiveService } from '@app/shared/shared-forms' |
6 | import { InstanceFollowService } from '@app/shared/shared-instance' | 6 | import { InstanceFollowService } from '@app/shared/shared-instance' |
@@ -62,9 +62,9 @@ export class FollowModalComponent extends FormReactive implements OnInit { | |||
62 | .subscribe({ | 62 | .subscribe({ |
63 | next: () => { | 63 | next: () => { |
64 | this.notifier.success( | 64 | this.notifier.success( |
65 | prepareIcu($localize`{count, plural, =1 {Follow request sent!} other {Follow requests sent!}}`)( | 65 | formatICU( |
66 | { count: hostsOrHandles.length }, | 66 | $localize`{count, plural, =1 {Follow request sent!} other {Follow requests sent!}}`, |
67 | $localize`Follow request(s) sent!` | 67 | { count: hostsOrHandles.length } |
68 | ) | 68 | ) |
69 | ) | 69 | ) |
70 | 70 | ||
diff --git a/client/src/app/+admin/follows/following-list/following-list.component.ts b/client/src/app/+admin/follows/following-list/following-list.component.ts index 71f2fbe66..6c8723c16 100644 --- a/client/src/app/+admin/follows/following-list/following-list.component.ts +++ b/client/src/app/+admin/follows/following-list/following-list.component.ts | |||
@@ -6,7 +6,7 @@ import { InstanceFollowService } from '@app/shared/shared-instance' | |||
6 | import { ActorFollow } from '@shared/models' | 6 | import { ActorFollow } from '@shared/models' |
7 | import { FollowModalComponent } from './follow-modal.component' | 7 | import { FollowModalComponent } from './follow-modal.component' |
8 | import { DropdownAction } from '@app/shared/shared-main' | 8 | import { DropdownAction } from '@app/shared/shared-main' |
9 | import { prepareIcu } from '@app/helpers' | 9 | import { formatICU } from '@app/helpers' |
10 | 10 | ||
11 | @Component({ | 11 | @Component({ |
12 | templateUrl: './following-list.component.html', | 12 | templateUrl: './following-list.component.html', |
@@ -64,9 +64,9 @@ export class FollowingListComponent extends RestTable <ActorFollow> implements O | |||
64 | async removeFollowing (follows: ActorFollow[]) { | 64 | async removeFollowing (follows: ActorFollow[]) { |
65 | const icuParams = { count: follows.length, entryName: this.buildFollowingName(follows[0]) } | 65 | const icuParams = { count: follows.length, entryName: this.buildFollowingName(follows[0]) } |
66 | 66 | ||
67 | const message = prepareIcu($localize`Do you really want to unfollow {count, plural, =1 {{entryName}?} other {{count} entries?}}`)( | 67 | const message = formatICU( |
68 | icuParams, | 68 | $localize`Do you really want to unfollow {count, plural, =1 {{entryName}?} other {{count} entries?}}`, |
69 | $localize`Do you really want to unfollow these entries?` | 69 | icuParams |
70 | ) | 70 | ) |
71 | 71 | ||
72 | const res = await this.confirmService.confirm(message, $localize`Unfollow`) | 72 | const res = await this.confirmService.confirm(message, $localize`Unfollow`) |
@@ -76,9 +76,9 @@ export class FollowingListComponent extends RestTable <ActorFollow> implements O | |||
76 | .subscribe({ | 76 | .subscribe({ |
77 | next: () => { | 77 | next: () => { |
78 | // eslint-disable-next-line max-len | 78 | // eslint-disable-next-line max-len |
79 | const message = prepareIcu($localize`You are not following {count, plural, =1 {{entryName} anymore.} other {these {count} entries anymore.}}`)( | 79 | const message = formatICU( |
80 | icuParams, | 80 | $localize`You are not following {count, plural, =1 {{entryName} anymore.} other {these {count} entries anymore.}}`, |
81 | $localize`You are not following them anymore.` | 81 | icuParams |
82 | ) | 82 | ) |
83 | 83 | ||
84 | this.notifier.success(message) | 84 | this.notifier.success(message) |
diff --git a/client/src/app/+admin/moderation/registration-list/registration-list.component.ts b/client/src/app/+admin/moderation/registration-list/registration-list.component.ts index 3ca1ceab8..35d9d13d7 100644 --- a/client/src/app/+admin/moderation/registration-list/registration-list.component.ts +++ b/client/src/app/+admin/moderation/registration-list/registration-list.component.ts | |||
@@ -2,7 +2,7 @@ import { SortMeta } from 'primeng/api' | |||
2 | import { Component, OnInit, ViewChild } from '@angular/core' | 2 | import { Component, OnInit, ViewChild } from '@angular/core' |
3 | import { ActivatedRoute, Router } from '@angular/router' | 3 | import { ActivatedRoute, Router } from '@angular/router' |
4 | import { ConfirmService, MarkdownService, Notifier, RestPagination, RestTable, ServerService } from '@app/core' | 4 | import { ConfirmService, MarkdownService, Notifier, RestPagination, RestTable, ServerService } from '@app/core' |
5 | import { prepareIcu } from '@app/helpers' | 5 | import { formatICU } from '@app/helpers' |
6 | import { AdvancedInputFilter } from '@app/shared/shared-forms' | 6 | import { AdvancedInputFilter } from '@app/shared/shared-forms' |
7 | import { DropdownAction } from '@app/shared/shared-main' | 7 | import { DropdownAction } from '@app/shared/shared-main' |
8 | import { UserRegistration, UserRegistrationState } from '@shared/models' | 8 | import { UserRegistration, UserRegistrationState } from '@shared/models' |
@@ -121,9 +121,9 @@ export class RegistrationListComponent extends RestTable <UserRegistration> impl | |||
121 | const icuParams = { count: registrations.length, username: registrations[0].username } | 121 | const icuParams = { count: registrations.length, username: registrations[0].username } |
122 | 122 | ||
123 | // eslint-disable-next-line max-len | 123 | // eslint-disable-next-line max-len |
124 | const message = prepareIcu($localize`Do you really want to delete {count, plural, =1 {{username} registration request?} other {{count} registration requests?}}`)( | 124 | const message = formatICU( |
125 | icuParams, | 125 | $localize`Do you really want to delete {count, plural, =1 {{username} registration request?} other {{count} registration requests?}}`, |
126 | $localize`Do you really want to delete these registration requests?` | 126 | icuParams |
127 | ) | 127 | ) |
128 | 128 | ||
129 | const res = await this.confirmService.confirm(message, $localize`Delete`) | 129 | const res = await this.confirmService.confirm(message, $localize`Delete`) |
@@ -133,9 +133,9 @@ export class RegistrationListComponent extends RestTable <UserRegistration> impl | |||
133 | .subscribe({ | 133 | .subscribe({ |
134 | next: () => { | 134 | next: () => { |
135 | // eslint-disable-next-line max-len | 135 | // eslint-disable-next-line max-len |
136 | const message = prepareIcu($localize`Removed {count, plural, =1 {{username} registration request} other {{count} registration requests}}`)( | 136 | const message = formatICU( |
137 | icuParams, | 137 | $localize`Removed {count, plural, =1 {{username} registration request} other {{count} registration requests}}`, |
138 | $localize`Registration requests removed` | 138 | icuParams |
139 | ) | 139 | ) |
140 | 140 | ||
141 | this.notifier.success(message) | 141 | this.notifier.success(message) |
diff --git a/client/src/app/+admin/overview/comments/video-comment-list.component.ts b/client/src/app/+admin/overview/comments/video-comment-list.component.ts index 28efdc076..b77072665 100644 --- a/client/src/app/+admin/overview/comments/video-comment-list.component.ts +++ b/client/src/app/+admin/overview/comments/video-comment-list.component.ts | |||
@@ -7,7 +7,7 @@ import { DropdownAction } from '@app/shared/shared-main' | |||
7 | import { BulkService } from '@app/shared/shared-moderation' | 7 | import { BulkService } from '@app/shared/shared-moderation' |
8 | import { VideoCommentAdmin, VideoCommentService } from '@app/shared/shared-video-comment' | 8 | import { VideoCommentAdmin, VideoCommentService } from '@app/shared/shared-video-comment' |
9 | import { FeedFormat, UserRight } from '@shared/models' | 9 | import { FeedFormat, UserRight } from '@shared/models' |
10 | import { prepareIcu } from '@app/helpers' | 10 | import { formatICU } from '@app/helpers' |
11 | 11 | ||
12 | @Component({ | 12 | @Component({ |
13 | selector: 'my-video-comment-list', | 13 | selector: 'my-video-comment-list', |
@@ -146,9 +146,9 @@ export class VideoCommentListComponent extends RestTable <VideoCommentAdmin> imp | |||
146 | .subscribe({ | 146 | .subscribe({ |
147 | next: () => { | 147 | next: () => { |
148 | this.notifier.success( | 148 | this.notifier.success( |
149 | prepareIcu($localize`{count, plural, =1 {1 comment deleted.} other {{count} comments deleted.}}`)( | 149 | formatICU( |
150 | { count: commentArgs.length }, | 150 | $localize`{count, plural, =1 {1 comment deleted.} other {{count} comments deleted.}}`, |
151 | $localize`${commentArgs.length} comment(s) deleted.` | 151 | { count: commentArgs.length } |
152 | ) | 152 | ) |
153 | ) | 153 | ) |
154 | 154 | ||
diff --git a/client/src/app/+admin/overview/users/user-list/user-list.component.ts b/client/src/app/+admin/overview/users/user-list/user-list.component.ts index 19420b748..5d5abf6f4 100644 --- a/client/src/app/+admin/overview/users/user-list/user-list.component.ts +++ b/client/src/app/+admin/overview/users/user-list/user-list.component.ts | |||
@@ -2,7 +2,7 @@ import { SortMeta } from 'primeng/api' | |||
2 | import { Component, OnInit, ViewChild } from '@angular/core' | 2 | import { Component, OnInit, ViewChild } from '@angular/core' |
3 | import { ActivatedRoute, Router } from '@angular/router' | 3 | import { ActivatedRoute, Router } from '@angular/router' |
4 | import { AuthService, ConfirmService, LocalStorageService, Notifier, RestPagination, RestTable, ServerService } from '@app/core' | 4 | import { AuthService, ConfirmService, LocalStorageService, Notifier, RestPagination, RestTable, ServerService } from '@app/core' |
5 | import { getAPIHost, prepareIcu } from '@app/helpers' | 5 | import { formatICU, getAPIHost } from '@app/helpers' |
6 | import { AdvancedInputFilter } from '@app/shared/shared-forms' | 6 | import { AdvancedInputFilter } from '@app/shared/shared-forms' |
7 | import { Actor, DropdownAction } from '@app/shared/shared-main' | 7 | import { Actor, DropdownAction } from '@app/shared/shared-main' |
8 | import { AccountMutedStatus, BlocklistService, UserBanModalComponent, UserModerationDisplayType } from '@app/shared/shared-moderation' | 8 | import { AccountMutedStatus, BlocklistService, UserBanModalComponent, UserModerationDisplayType } from '@app/shared/shared-moderation' |
@@ -210,9 +210,9 @@ export class UserListComponent extends RestTable <User> implements OnInit { | |||
210 | 210 | ||
211 | async unbanUsers (users: User[]) { | 211 | async unbanUsers (users: User[]) { |
212 | const res = await this.confirmService.confirm( | 212 | const res = await this.confirmService.confirm( |
213 | prepareIcu($localize`Do you really want to unban {count, plural, =1 {1 user} other {{count} users}}?`)( | 213 | formatICU( |
214 | { count: users.length }, | 214 | $localize`Do you really want to unban {count, plural, =1 {1 user} other {{count} users}}?`, |
215 | $localize`Do you really want to unban ${users.length} users?` | 215 | { count: users.length } |
216 | ), | 216 | ), |
217 | $localize`Unban` | 217 | $localize`Unban` |
218 | ) | 218 | ) |
@@ -223,9 +223,9 @@ export class UserListComponent extends RestTable <User> implements OnInit { | |||
223 | .subscribe({ | 223 | .subscribe({ |
224 | next: () => { | 224 | next: () => { |
225 | this.notifier.success( | 225 | this.notifier.success( |
226 | prepareIcu($localize`{count, plural, =1 {1 user unbanned.} other {{count} users unbanned.}}`)( | 226 | formatICU( |
227 | { count: users.length }, | 227 | $localize`{count, plural, =1 {1 user unbanned.} other {{count} users unbanned.}}`, |
228 | $localize`${users.length} users unbanned.` | 228 | { count: users.length } |
229 | ) | 229 | ) |
230 | ) | 230 | ) |
231 | this.reloadData() | 231 | this.reloadData() |
@@ -252,9 +252,9 @@ export class UserListComponent extends RestTable <User> implements OnInit { | |||
252 | .subscribe({ | 252 | .subscribe({ |
253 | next: () => { | 253 | next: () => { |
254 | this.notifier.success( | 254 | this.notifier.success( |
255 | prepareIcu($localize`{count, plural, =1 {1 user deleted.} other {{count} users deleted.}}`)( | 255 | formatICU( |
256 | { count: users.length }, | 256 | $localize`{count, plural, =1 {1 user deleted.} other {{count} users deleted.}}`, |
257 | $localize`${users.length} users deleted.` | 257 | { count: users.length } |
258 | ) | 258 | ) |
259 | ) | 259 | ) |
260 | 260 | ||
@@ -270,9 +270,9 @@ export class UserListComponent extends RestTable <User> implements OnInit { | |||
270 | .subscribe({ | 270 | .subscribe({ |
271 | next: () => { | 271 | next: () => { |
272 | this.notifier.success( | 272 | this.notifier.success( |
273 | prepareIcu($localize`{count, plural, =1 {1 user email set as verified.} other {{count} user emails set as verified.}}`)( | 273 | formatICU( |
274 | { count: users.length }, | 274 | $localize`{count, plural, =1 {1 user email set as verified.} other {{count} user emails set as verified.}}`, |
275 | $localize`${users.length} users email set as verified.` | 275 | { count: users.length } |
276 | ) | 276 | ) |
277 | ) | 277 | ) |
278 | 278 | ||
diff --git a/client/src/app/+admin/overview/videos/video-list.component.ts b/client/src/app/+admin/overview/videos/video-list.component.ts index ebf82ce16..e9c526193 100644 --- a/client/src/app/+admin/overview/videos/video-list.component.ts +++ b/client/src/app/+admin/overview/videos/video-list.component.ts | |||
@@ -3,7 +3,7 @@ import { finalize } from 'rxjs/operators' | |||
3 | import { Component, OnInit, ViewChild } from '@angular/core' | 3 | import { Component, OnInit, ViewChild } from '@angular/core' |
4 | import { ActivatedRoute, Router } from '@angular/router' | 4 | import { ActivatedRoute, Router } from '@angular/router' |
5 | import { AuthService, ConfirmService, Notifier, RestPagination, RestTable } from '@app/core' | 5 | import { AuthService, ConfirmService, Notifier, RestPagination, RestTable } from '@app/core' |
6 | import { prepareIcu } from '@app/helpers' | 6 | import { formatICU } from '@app/helpers' |
7 | import { AdvancedInputFilter } from '@app/shared/shared-forms' | 7 | import { AdvancedInputFilter } from '@app/shared/shared-forms' |
8 | import { DropdownAction, Video, VideoService } from '@app/shared/shared-main' | 8 | import { DropdownAction, Video, VideoService } from '@app/shared/shared-main' |
9 | import { VideoBlockComponent, VideoBlockService } from '@app/shared/shared-moderation' | 9 | import { VideoBlockComponent, VideoBlockService } from '@app/shared/shared-moderation' |
@@ -219,9 +219,9 @@ export class VideoListComponent extends RestTable <Video> implements OnInit { | |||
219 | } | 219 | } |
220 | 220 | ||
221 | private async removeVideos (videos: Video[]) { | 221 | private async removeVideos (videos: Video[]) { |
222 | const message = prepareIcu($localize`Are you sure you want to delete {count, plural, =1 {this video} other {these {count} videos}}?`)( | 222 | const message = formatICU( |
223 | { count: videos.length }, | 223 | $localize`Are you sure you want to delete {count, plural, =1 {this video} other {these {count} videos}}?`, |
224 | $localize`Are you sure you want to delete these ${videos.length} videos?` | 224 | { count: videos.length } |
225 | ) | 225 | ) |
226 | 226 | ||
227 | const res = await this.confirmService.confirm(message, $localize`Delete`) | 227 | const res = await this.confirmService.confirm(message, $localize`Delete`) |
@@ -231,9 +231,9 @@ export class VideoListComponent extends RestTable <Video> implements OnInit { | |||
231 | .subscribe({ | 231 | .subscribe({ |
232 | next: () => { | 232 | next: () => { |
233 | this.notifier.success( | 233 | this.notifier.success( |
234 | prepareIcu($localize`Deleted {count, plural, =1 {1 video} other {{count} videos}}.`)( | 234 | formatICU( |
235 | { count: videos.length }, | 235 | $localize`Deleted {count, plural, =1 {1 video} other {{count} videos}}.`, |
236 | $localize`Deleted ${videos.length} videos.` | 236 | { count: videos.length } |
237 | ) | 237 | ) |
238 | ) | 238 | ) |
239 | 239 | ||
@@ -249,9 +249,9 @@ export class VideoListComponent extends RestTable <Video> implements OnInit { | |||
249 | .subscribe({ | 249 | .subscribe({ |
250 | next: () => { | 250 | next: () => { |
251 | this.notifier.success( | 251 | this.notifier.success( |
252 | prepareIcu($localize`Unblocked {count, plural, =1 {1 video} other {{count} videos}}.`)( | 252 | formatICU( |
253 | { count: videos.length }, | 253 | $localize`Unblocked {count, plural, =1 {1 video} other {{count} videos}}.`, |
254 | $localize`Unblocked ${videos.length} videos.` | 254 | { count: videos.length } |
255 | ) | 255 | ) |
256 | ) | 256 | ) |
257 | 257 | ||
@@ -267,15 +267,15 @@ export class VideoListComponent extends RestTable <Video> implements OnInit { | |||
267 | 267 | ||
268 | if (type === 'hls') { | 268 | if (type === 'hls') { |
269 | // eslint-disable-next-line max-len | 269 | // eslint-disable-next-line max-len |
270 | message = prepareIcu($localize`Are you sure you want to delete {count, plural, =1 {1 HLS streaming playlist} other {{count} HLS streaming playlists}}?`)( | 270 | message = formatICU( |
271 | { count: videos.length }, | 271 | $localize`Are you sure you want to delete {count, plural, =1 {1 HLS streaming playlist} other {{count} HLS streaming playlists}}?`, |
272 | $localize`Are you sure you want to delete ${videos.length} HLS streaming playlists?` | 272 | { count: videos.length } |
273 | ) | 273 | ) |
274 | } else { | 274 | } else { |
275 | // eslint-disable-next-line max-len | 275 | // eslint-disable-next-line max-len |
276 | message = prepareIcu($localize`Are you sure you want to delete WebTorrent files of {count, plural, =1 {1 video} other {{count} videos}}?`)( | 276 | message = formatICU( |
277 | { count: videos.length }, | 277 | $localize`Are you sure you want to delete WebTorrent files of {count, plural, =1 {1 video} other {{count} videos}}?`, |
278 | $localize`Are you sure you want to delete WebTorrent files of ${videos.length} videos?` | 278 | { count: videos.length } |
279 | ) | 279 | ) |
280 | } | 280 | } |
281 | 281 | ||
diff --git a/client/src/app/+admin/system/runners/runner-job-list/runner-job-list.component.ts b/client/src/app/+admin/system/runners/runner-job-list/runner-job-list.component.ts index 8ba956eb8..8994c1d00 100644 --- a/client/src/app/+admin/system/runners/runner-job-list/runner-job-list.component.ts +++ b/client/src/app/+admin/system/runners/runner-job-list/runner-job-list.component.ts | |||
@@ -1,7 +1,7 @@ | |||
1 | import { SortMeta } from 'primeng/api' | 1 | import { SortMeta } from 'primeng/api' |
2 | import { Component, OnInit } from '@angular/core' | 2 | import { Component, OnInit } from '@angular/core' |
3 | import { ConfirmService, Notifier, RestPagination, RestTable } from '@app/core' | 3 | import { ConfirmService, Notifier, RestPagination, RestTable } from '@app/core' |
4 | import { prepareIcu } from '@app/helpers' | 4 | import { formatICU } from '@app/helpers' |
5 | import { DropdownAction } from '@app/shared/shared-main' | 5 | import { DropdownAction } from '@app/shared/shared-main' |
6 | import { RunnerJob, RunnerJobState } from '@shared/models' | 6 | import { RunnerJob, RunnerJobState } from '@shared/models' |
7 | import { RunnerJobFormatted, RunnerService } from '../runner.service' | 7 | import { RunnerJobFormatted, RunnerService } from '../runner.service' |
@@ -57,9 +57,10 @@ export class RunnerJobListComponent extends RestTable <RunnerJob> implements OnI | |||
57 | } | 57 | } |
58 | 58 | ||
59 | async cancelJobs (jobs: RunnerJob[]) { | 59 | async cancelJobs (jobs: RunnerJob[]) { |
60 | const message = prepareIcu( | 60 | const message = formatICU( |
61 | $localize`Do you really want to cancel {count, plural, =1 {this job} other {{count} jobs}}? Children jobs will also be cancelled.` | 61 | $localize`Do you really want to cancel {count, plural, =1 {this job} other {{count} jobs}}? Children jobs will also be cancelled.`, |
62 | )({ count: jobs.length }, $localize`Do you really want to cancel these jobs? Children jobs will also be cancelled.`) | 62 | { count: jobs.length } |
63 | ) | ||
63 | 64 | ||
64 | const res = await this.confirmService.confirm(message, $localize`Cancel`) | 65 | const res = await this.confirmService.confirm(message, $localize`Cancel`) |
65 | 66 | ||
diff --git a/client/src/app/+my-library/my-videos/my-videos.component.ts b/client/src/app/+my-library/my-videos/my-videos.component.ts index 57b8bdf7d..1827d6a0b 100644 --- a/client/src/app/+my-library/my-videos/my-videos.component.ts +++ b/client/src/app/+my-library/my-videos/my-videos.component.ts | |||
@@ -5,7 +5,7 @@ import { Component, OnInit, ViewChild } from '@angular/core' | |||
5 | import { ActivatedRoute, Router } from '@angular/router' | 5 | import { ActivatedRoute, Router } from '@angular/router' |
6 | import { AuthService, ComponentPagination, ConfirmService, Notifier, ScreenService, ServerService, User } from '@app/core' | 6 | import { AuthService, ComponentPagination, ConfirmService, Notifier, ScreenService, ServerService, User } from '@app/core' |
7 | import { DisableForReuseHook } from '@app/core/routing/disable-for-reuse-hook' | 7 | import { DisableForReuseHook } from '@app/core/routing/disable-for-reuse-hook' |
8 | import { immutableAssign, prepareIcu } from '@app/helpers' | 8 | import { immutableAssign, formatICU } from '@app/helpers' |
9 | import { AdvancedInputFilter } from '@app/shared/shared-forms' | 9 | import { AdvancedInputFilter } from '@app/shared/shared-forms' |
10 | import { DropdownAction, Video, VideoService } from '@app/shared/shared-main' | 10 | import { DropdownAction, Video, VideoService } from '@app/shared/shared-main' |
11 | import { LiveStreamInformationComponent } from '@app/shared/shared-video-live' | 11 | import { LiveStreamInformationComponent } from '@app/shared/shared-video-live' |
@@ -184,9 +184,9 @@ export class MyVideosComponent implements OnInit, DisableForReuseHook { | |||
184 | .map(([ k, _v ]) => parseInt(k, 10)) | 184 | .map(([ k, _v ]) => parseInt(k, 10)) |
185 | 185 | ||
186 | const res = await this.confirmService.confirm( | 186 | const res = await this.confirmService.confirm( |
187 | prepareIcu($localize`Do you really want to delete {length, plural, =1 {this video} other {{length} videos}}?`)( | 187 | formatICU( |
188 | { length: toDeleteVideosIds.length }, | 188 | $localize`Do you really want to delete {length, plural, =1 {this video} other {{length} videos}}?`, |
189 | $localize`Do you really want to delete ${toDeleteVideosIds.length} videos?` | 189 | { length: toDeleteVideosIds.length } |
190 | ), | 190 | ), |
191 | $localize`Delete` | 191 | $localize`Delete` |
192 | ) | 192 | ) |
@@ -205,9 +205,9 @@ export class MyVideosComponent implements OnInit, DisableForReuseHook { | |||
205 | .subscribe({ | 205 | .subscribe({ |
206 | next: () => { | 206 | next: () => { |
207 | this.notifier.success( | 207 | this.notifier.success( |
208 | prepareIcu($localize`{length, plural, =1 {Video has been deleted} other {{length} videos have been deleted}}`)( | 208 | formatICU( |
209 | { length: toDeleteVideosIds.length }, | 209 | $localize`{length, plural, =1 {Video has been deleted} other {{length} videos have been deleted}}`, |
210 | $localize`${toDeleteVideosIds.length} have been deleted.` | 210 | { length: toDeleteVideosIds.length } |
211 | ) | 211 | ) |
212 | ) | 212 | ) |
213 | 213 | ||
diff --git a/client/src/app/app.module.ts b/client/src/app/app.module.ts index 7e4fac730..9339865f1 100644 --- a/client/src/app/app.module.ts +++ b/client/src/app/app.module.ts | |||
@@ -12,13 +12,14 @@ import { CoreModule, PluginService, RedirectService, ServerService } from './cor | |||
12 | import { EmptyComponent } from './empty.component' | 12 | import { EmptyComponent } from './empty.component' |
13 | import { HeaderComponent, SearchTypeaheadComponent, SuggestionComponent } from './header' | 13 | import { HeaderComponent, SearchTypeaheadComponent, SuggestionComponent } from './header' |
14 | import { HighlightPipe } from './header/highlight.pipe' | 14 | import { HighlightPipe } from './header/highlight.pipe' |
15 | import { polyfillICU } from './helpers' | ||
15 | import { LanguageChooserComponent, MenuComponent, NotificationComponent } from './menu' | 16 | import { LanguageChooserComponent, MenuComponent, NotificationComponent } from './menu' |
17 | import { AccountSetupWarningModalComponent } from './modal/account-setup-warning-modal.component' | ||
18 | import { AdminWelcomeModalComponent } from './modal/admin-welcome-modal.component' | ||
16 | import { ConfirmComponent } from './modal/confirm.component' | 19 | import { ConfirmComponent } from './modal/confirm.component' |
17 | import { CustomModalComponent } from './modal/custom-modal.component' | 20 | import { CustomModalComponent } from './modal/custom-modal.component' |
18 | import { InstanceConfigWarningModalComponent } from './modal/instance-config-warning-modal.component' | 21 | import { InstanceConfigWarningModalComponent } from './modal/instance-config-warning-modal.component' |
19 | import { QuickSettingsModalComponent } from './modal/quick-settings-modal.component' | 22 | import { QuickSettingsModalComponent } from './modal/quick-settings-modal.component' |
20 | import { AdminWelcomeModalComponent } from './modal/admin-welcome-modal.component' | ||
21 | import { AccountSetupWarningModalComponent } from './modal/account-setup-warning-modal.component' | ||
22 | import { SharedActorImageModule } from './shared/shared-actor-image/shared-actor-image.module' | 23 | import { SharedActorImageModule } from './shared/shared-actor-image/shared-actor-image.module' |
23 | import { SharedFormModule } from './shared/shared-forms' | 24 | import { SharedFormModule } from './shared/shared-forms' |
24 | import { SharedGlobalIconModule } from './shared/shared-icons' | 25 | import { SharedGlobalIconModule } from './shared/shared-icons' |
@@ -90,6 +91,11 @@ export function loadConfigFactory (server: ServerService, pluginService: PluginS | |||
90 | useFactory: loadConfigFactory, | 91 | useFactory: loadConfigFactory, |
91 | deps: [ ServerService, PluginService, RedirectService ], | 92 | deps: [ ServerService, PluginService, RedirectService ], |
92 | multi: true | 93 | multi: true |
94 | }, | ||
95 | { | ||
96 | provide: APP_INITIALIZER, | ||
97 | useFactory: () => polyfillICU, | ||
98 | multi: true | ||
93 | } | 99 | } |
94 | ] | 100 | ] |
95 | }) | 101 | }) |
diff --git a/client/src/app/helpers/i18n-utils.ts b/client/src/app/helpers/i18n-utils.ts index b7d73d16b..9e22bb4c1 100644 --- a/client/src/app/helpers/i18n-utils.ts +++ b/client/src/app/helpers/i18n-utils.ts | |||
@@ -1,4 +1,6 @@ | |||
1 | import IntlMessageFormat from 'intl-messageformat' | 1 | import IntlMessageFormat from 'intl-messageformat' |
2 | import { shouldPolyfill as shouldPolyfillLocale } from '@formatjs/intl-locale/should-polyfill' | ||
3 | import { shouldPolyfill as shouldPolyfillPlural } from '@formatjs/intl-pluralrules/should-polyfill' | ||
2 | import { logger } from '@root-helpers/logger' | 4 | import { logger } from '@root-helpers/logger' |
3 | import { environment } from '../../environments/environment' | 5 | import { environment } from '../../environments/environment' |
4 | 6 | ||
@@ -10,31 +12,68 @@ function getDevLocale () { | |||
10 | return 'fr-FR' | 12 | return 'fr-FR' |
11 | } | 13 | } |
12 | 14 | ||
13 | function prepareIcu (icu: string) { | 15 | async function polyfillICU () { |
14 | let alreadyWarned = false | 16 | // Important to be in this order, Plural needs Locale (https://formatjs.io/docs/polyfills/intl-pluralrules) |
17 | await polyfillICULocale() | ||
18 | await polyfillICUPlural() | ||
19 | } | ||
15 | 20 | ||
16 | try { | 21 | async function polyfillICULocale () { |
17 | const msg = new IntlMessageFormat(icu, $localize.locale) | 22 | // This locale is supported |
23 | if (shouldPolyfillLocale()) { | ||
24 | // TODO: remove, it's only needed to support Plural polyfill and so iOS 12 | ||
25 | console.log('Loading Intl Locale polyfill for ' + $localize.locale) | ||
26 | |||
27 | await import('@formatjs/intl-locale/polyfill') | ||
28 | } | ||
29 | } | ||
30 | |||
31 | async function polyfillICUPlural () { | ||
32 | const unsupportedLocale = shouldPolyfillPlural($localize.locale) | ||
33 | |||
34 | // This locale is supported | ||
35 | if (!unsupportedLocale) { | ||
36 | return | ||
37 | } | ||
18 | 38 | ||
19 | return (context: { [id: string]: number | string }, fallback: string) => { | 39 | // TODO: remove, it's only needed to support iOS 12 |
20 | try { | 40 | console.log('Loading Intl Plural rules polyfill for ' + $localize.locale) |
21 | return msg.format(context) as string | ||
22 | } catch (err) { | ||
23 | if (!alreadyWarned) logger.warn(`Cannot format ICU ${icu}.`, err) | ||
24 | 41 | ||
25 | alreadyWarned = true | 42 | // Load the polyfill 1st BEFORE loading data |
26 | return fallback | 43 | await import('@formatjs/intl-pluralrules/polyfill-force') |
27 | } | 44 | // Degraded mode, so only load the en local data |
45 | await import(`@formatjs/intl-pluralrules/locale-data/en.js`) | ||
46 | } | ||
47 | |||
48 | // --------------------------------------------------------------------------- | ||
49 | |||
50 | const icuCache = new Map<string, IntlMessageFormat>() | ||
51 | const icuWarnings = new Set<string>() | ||
52 | const fallback = 'String translation error' | ||
53 | |||
54 | function formatICU (icu: string, context: { [id: string]: number | string }) { | ||
55 | try { | ||
56 | let msg = icuCache.get(icu) | ||
57 | |||
58 | if (!msg) { | ||
59 | msg = new IntlMessageFormat(icu, $localize.locale) | ||
60 | icuCache.set(icu, msg) | ||
28 | } | 61 | } |
62 | |||
63 | return msg.format(context) as string | ||
29 | } catch (err) { | 64 | } catch (err) { |
30 | logger.warn(`Cannot build intl message ${icu}.`, err) | 65 | if (!icuWarnings.has(icu)) { |
66 | logger.warn(`Cannot format ICU ${icu}.`, err) | ||
67 | } | ||
31 | 68 | ||
32 | return (_context: unknown, fallback: string) => fallback | 69 | icuWarnings.add(icu) |
70 | return fallback | ||
33 | } | 71 | } |
34 | } | 72 | } |
35 | 73 | ||
36 | export { | 74 | export { |
37 | getDevLocale, | 75 | getDevLocale, |
38 | prepareIcu, | 76 | polyfillICU, |
77 | formatICU, | ||
39 | isOnDevLocale | 78 | isOnDevLocale |
40 | } | 79 | } |
diff --git a/client/src/app/shared/shared-forms/select/select-checkbox-all.component.ts b/client/src/app/shared/shared-forms/select/select-checkbox-all.component.ts index 2c3226f68..8b6cd091a 100644 --- a/client/src/app/shared/shared-forms/select/select-checkbox-all.component.ts +++ b/client/src/app/shared/shared-forms/select/select-checkbox-all.component.ts | |||
@@ -1,7 +1,7 @@ | |||
1 | import { Component, forwardRef, Input } from '@angular/core' | 1 | import { Component, forwardRef, Input } from '@angular/core' |
2 | import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms' | 2 | import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms' |
3 | import { Notifier } from '@app/core' | 3 | import { Notifier } from '@app/core' |
4 | import { prepareIcu } from '@app/helpers' | 4 | import { formatICU } from '@app/helpers' |
5 | import { SelectOptionsItem } from '../../../../types/select-options-item.model' | 5 | import { SelectOptionsItem } from '../../../../types/select-options-item.model' |
6 | import { ItemSelectCheckboxValue } from './select-checkbox.component' | 6 | import { ItemSelectCheckboxValue } from './select-checkbox.component' |
7 | 7 | ||
@@ -80,9 +80,9 @@ export class SelectCheckboxAllComponent implements ControlValueAccessor { | |||
80 | 80 | ||
81 | if (outputItems.length >= this.maxItems) { | 81 | if (outputItems.length >= this.maxItems) { |
82 | this.notifier.error( | 82 | this.notifier.error( |
83 | prepareIcu($localize`You can't select more than {maxItems, plural, =1 {1 item} other {{maxItems} items}}`)( | 83 | formatICU( |
84 | { maxItems: this.maxItems }, | 84 | $localize`You can't select more than {maxItems, plural, =1 {1 item} other {{maxItems} items}}`, |
85 | $localize`You can't select more than ${this.maxItems} items` | 85 | { maxItems: this.maxItems } |
86 | ) | 86 | ) |
87 | ) | 87 | ) |
88 | 88 | ||
diff --git a/client/src/app/shared/shared-instance/instance-features-table.component.ts b/client/src/app/shared/shared-instance/instance-features-table.component.ts index 2e63f6c17..ab1b1458a 100644 --- a/client/src/app/shared/shared-instance/instance-features-table.component.ts +++ b/client/src/app/shared/shared-instance/instance-features-table.component.ts | |||
@@ -1,6 +1,6 @@ | |||
1 | import { Component, OnInit } from '@angular/core' | 1 | import { Component, OnInit } from '@angular/core' |
2 | import { ServerService } from '@app/core' | 2 | import { ServerService } from '@app/core' |
3 | import { prepareIcu } from '@app/helpers' | 3 | import { formatICU } from '@app/helpers' |
4 | import { ServerConfig } from '@shared/models' | 4 | import { ServerConfig } from '@shared/models' |
5 | 5 | ||
6 | @Component({ | 6 | @Component({ |
@@ -71,17 +71,17 @@ export class InstanceFeaturesTableComponent implements OnInit { | |||
71 | const hours = Math.floor(seconds / 3600) | 71 | const hours = Math.floor(seconds / 3600) |
72 | 72 | ||
73 | if (hours !== 0) { | 73 | if (hours !== 0) { |
74 | return prepareIcu($localize`~ {hours, plural, =1 {1 hour} other {{hours} hours}}`)( | 74 | return formatICU( |
75 | { hours }, | 75 | $localize`~ {hours, plural, =1 {1 hour} other {{hours} hours}}`, |
76 | $localize`~ ${hours} hours` | 76 | { hours } |
77 | ) | 77 | ) |
78 | } | 78 | } |
79 | 79 | ||
80 | const minutes = Math.floor(seconds % 3600 / 60) | 80 | const minutes = Math.floor(seconds % 3600 / 60) |
81 | 81 | ||
82 | return prepareIcu($localize`~ {minutes, plural, =1 {1 minute} other {{minutes} minutes}}`)( | 82 | return formatICU( |
83 | { minutes }, | 83 | $localize`~ {minutes, plural, =1 {1 minute} other {{minutes} minutes}}`, |
84 | $localize`~ ${minutes} minutes` | 84 | { minutes } |
85 | ) | 85 | ) |
86 | } | 86 | } |
87 | 87 | ||
diff --git a/client/src/app/shared/shared-main/angular/from-now.pipe.ts b/client/src/app/shared/shared-main/angular/from-now.pipe.ts index dc6a25e83..4ff244bbb 100644 --- a/client/src/app/shared/shared-main/angular/from-now.pipe.ts +++ b/client/src/app/shared/shared-main/angular/from-now.pipe.ts | |||
@@ -1,14 +1,9 @@ | |||
1 | import { Pipe, PipeTransform } from '@angular/core' | 1 | import { Pipe, PipeTransform } from '@angular/core' |
2 | import { prepareIcu } from '@app/helpers' | 2 | import { formatICU } from '@app/helpers' |
3 | 3 | ||
4 | // Thanks: https://stackoverflow.com/questions/3177836/how-to-format-time-since-xxx-e-g-4-minutes-ago-similar-to-stack-exchange-site | 4 | // Thanks: https://stackoverflow.com/questions/3177836/how-to-format-time-since-xxx-e-g-4-minutes-ago-similar-to-stack-exchange-site |
5 | @Pipe({ name: 'myFromNow' }) | 5 | @Pipe({ name: 'myFromNow' }) |
6 | export class FromNowPipe implements PipeTransform { | 6 | export class FromNowPipe implements PipeTransform { |
7 | private yearICU = prepareIcu($localize`{interval, plural, =1 {1 year ago} other {{interval} years ago}}`) | ||
8 | private monthICU = prepareIcu($localize`{interval, plural, =1 {1 month ago} other {{interval} months ago}}`) | ||
9 | private weekICU = prepareIcu($localize`{interval, plural, =1 {1 week ago} other {{interval} weeks ago}}`) | ||
10 | private dayICU = prepareIcu($localize`{interval, plural, =1 {1 day ago} other {{interval} days ago}}`) | ||
11 | private hourICU = prepareIcu($localize`{interval, plural, =1 {1 hour ago} other {{interval} hours ago}}`) | ||
12 | 7 | ||
13 | transform (arg: number | Date | string) { | 8 | transform (arg: number | Date | string) { |
14 | const argDate = new Date(arg) | 9 | const argDate = new Date(arg) |
@@ -16,7 +11,7 @@ export class FromNowPipe implements PipeTransform { | |||
16 | 11 | ||
17 | let interval = Math.floor(seconds / 31536000) | 12 | let interval = Math.floor(seconds / 31536000) |
18 | if (interval >= 1) { | 13 | if (interval >= 1) { |
19 | return this.yearICU({ interval }, $localize`${interval} year(s) ago`) | 14 | return formatICU($localize`{interval, plural, =1 {1 year ago} other {{interval} years ago}}`, { interval }) |
20 | } | 15 | } |
21 | 16 | ||
22 | interval = Math.floor(seconds / 2419200) | 17 | interval = Math.floor(seconds / 2419200) |
@@ -25,7 +20,7 @@ export class FromNowPipe implements PipeTransform { | |||
25 | if (interval >= 12) return $localize`1 year ago` | 20 | if (interval >= 12) return $localize`1 year ago` |
26 | 21 | ||
27 | if (interval >= 1) { | 22 | if (interval >= 1) { |
28 | return this.monthICU({ interval }, $localize`${interval} month(s) ago`) | 23 | return formatICU($localize`{interval, plural, =1 {1 month ago} other {{interval} months ago}}`, { interval }) |
29 | } | 24 | } |
30 | 25 | ||
31 | interval = Math.floor(seconds / 604800) | 26 | interval = Math.floor(seconds / 604800) |
@@ -34,17 +29,17 @@ export class FromNowPipe implements PipeTransform { | |||
34 | if (interval >= 4) return $localize`1 month ago` | 29 | if (interval >= 4) return $localize`1 month ago` |
35 | 30 | ||
36 | if (interval >= 1) { | 31 | if (interval >= 1) { |
37 | return this.weekICU({ interval }, $localize`${interval} week(s) ago`) | 32 | return formatICU($localize`{interval, plural, =1 {1 week ago} other {{interval} weeks ago}}`, { interval }) |
38 | } | 33 | } |
39 | 34 | ||
40 | interval = Math.floor(seconds / 86400) | 35 | interval = Math.floor(seconds / 86400) |
41 | if (interval >= 1) { | 36 | if (interval >= 1) { |
42 | return this.dayICU({ interval }, $localize`${interval} day(s) ago`) | 37 | return formatICU($localize`{interval, plural, =1 {1 day ago} other {{interval} days ago}}`, { interval }) |
43 | } | 38 | } |
44 | 39 | ||
45 | interval = Math.floor(seconds / 3600) | 40 | interval = Math.floor(seconds / 3600) |
46 | if (interval >= 1) { | 41 | if (interval >= 1) { |
47 | return this.hourICU({ interval }, $localize`${interval} hour(s) ago`) | 42 | return formatICU($localize`{interval, plural, =1 {1 hour ago} other {{interval} hours ago}}`, { interval }) |
48 | } | 43 | } |
49 | 44 | ||
50 | interval = Math.floor(seconds / 60) | 45 | interval = Math.floor(seconds / 60) |
diff --git a/client/src/app/shared/shared-main/video/video.model.ts b/client/src/app/shared/shared-main/video/video.model.ts index 24c00c3d5..e94087dbe 100644 --- a/client/src/app/shared/shared-main/video/video.model.ts +++ b/client/src/app/shared/shared-main/video/video.model.ts | |||
@@ -1,6 +1,6 @@ | |||
1 | import { AuthUser } from '@app/core' | 1 | import { AuthUser } from '@app/core' |
2 | import { User } from '@app/core/users/user.model' | 2 | import { User } from '@app/core/users/user.model' |
3 | import { durationToString, getAbsoluteAPIUrl, getAbsoluteEmbedUrl, prepareIcu } from '@app/helpers' | 3 | import { durationToString, formatICU, getAbsoluteAPIUrl, getAbsoluteEmbedUrl } from '@app/helpers' |
4 | import { Actor } from '@app/shared/shared-main/account/actor.model' | 4 | import { Actor } from '@app/shared/shared-main/account/actor.model' |
5 | import { buildVideoWatchPath, getAllFiles } from '@shared/core-utils' | 5 | import { buildVideoWatchPath, getAllFiles } from '@shared/core-utils' |
6 | import { peertubeTranslate } from '@shared/core-utils/i18n' | 6 | import { peertubeTranslate } from '@shared/core-utils/i18n' |
@@ -19,9 +19,6 @@ import { | |||
19 | } from '@shared/models' | 19 | } from '@shared/models' |
20 | 20 | ||
21 | export class Video implements VideoServerModel { | 21 | export class Video implements VideoServerModel { |
22 | private static readonly viewsICU = prepareIcu($localize`{views, plural, =0 {No view} =1 {1 view} other {{views} views}}`) | ||
23 | private static readonly viewersICU = prepareIcu($localize`{viewers, plural, =0 {No viewers} =1 {1 viewer} other {{viewers} viewers}}`) | ||
24 | |||
25 | byVideoChannel: string | 22 | byVideoChannel: string |
26 | byAccount: string | 23 | byAccount: string |
27 | 24 | ||
@@ -290,9 +287,9 @@ export class Video implements VideoServerModel { | |||
290 | 287 | ||
291 | getExactNumberOfViews () { | 288 | getExactNumberOfViews () { |
292 | if (this.isLive) { | 289 | if (this.isLive) { |
293 | return Video.viewersICU({ viewers: this.viewers }, $localize`${this.viewers} viewer(s)`) | 290 | return formatICU($localize`{viewers, plural, =0 {No viewers} =1 {1 viewer} other {{viewers} viewers}}`, { viewers: this.viewers }) |
294 | } | 291 | } |
295 | 292 | ||
296 | return Video.viewsICU({ views: this.views }, $localize`{${this.views} view(s)}`) | 293 | return formatICU($localize`{views, plural, =0 {No view} =1 {1 view} other {{views} views}}`, { views: this.views }) |
297 | } | 294 | } |
298 | } | 295 | } |
diff --git a/client/src/app/shared/shared-moderation/user-ban-modal.component.ts b/client/src/app/shared/shared-moderation/user-ban-modal.component.ts index 27dcf043a..34295c34a 100644 --- a/client/src/app/shared/shared-moderation/user-ban-modal.component.ts +++ b/client/src/app/shared/shared-moderation/user-ban-modal.component.ts | |||
@@ -1,7 +1,7 @@ | |||
1 | import { forkJoin } from 'rxjs' | 1 | import { forkJoin } from 'rxjs' |
2 | import { Component, EventEmitter, OnInit, Output, ViewChild } from '@angular/core' | 2 | import { Component, EventEmitter, OnInit, Output, ViewChild } from '@angular/core' |
3 | import { Notifier } from '@app/core' | 3 | import { Notifier } from '@app/core' |
4 | import { prepareIcu } from '@app/helpers' | 4 | import { formatICU } from '@app/helpers' |
5 | import { FormReactive, FormReactiveService } from '@app/shared/shared-forms' | 5 | import { FormReactive, FormReactiveService } from '@app/shared/shared-forms' |
6 | import { NgbModal } from '@ng-bootstrap/ng-bootstrap' | 6 | import { NgbModal } from '@ng-bootstrap/ng-bootstrap' |
7 | import { NgbModalRef } from '@ng-bootstrap/ng-bootstrap/modal/modal-ref' | 7 | import { NgbModalRef } from '@ng-bootstrap/ng-bootstrap/modal/modal-ref' |
@@ -67,9 +67,9 @@ export class UserBanModalComponent extends FormReactive implements OnInit { | |||
67 | let message: string | 67 | let message: string |
68 | 68 | ||
69 | if (Array.isArray(this.usersToBan)) { | 69 | if (Array.isArray(this.usersToBan)) { |
70 | message = prepareIcu($localize`{count, plural, =1 {1 user banned.} other {{count} users banned.}}`)( | 70 | message = formatICU( |
71 | { count: this.usersToBan.length }, | 71 | $localize`{count, plural, =1 {1 user banned.} other {{count} users banned.}}`, |
72 | $localize`${this.usersToBan.length} users banned.` | 72 | { count: this.usersToBan.length } |
73 | ) | 73 | ) |
74 | } else { | 74 | } else { |
75 | message = $localize`User ${this.usersToBan.username} banned.` | 75 | message = $localize`User ${this.usersToBan.username} banned.` |
@@ -88,9 +88,9 @@ export class UserBanModalComponent extends FormReactive implements OnInit { | |||
88 | 88 | ||
89 | getModalTitle () { | 89 | getModalTitle () { |
90 | if (Array.isArray(this.usersToBan)) { | 90 | if (Array.isArray(this.usersToBan)) { |
91 | return prepareIcu($localize`Ban {count, plural, =1 {1 user} other {{count} users}}`)( | 91 | return formatICU( |
92 | { count: this.usersToBan.length }, | 92 | $localize`Ban {count, plural, =1 {1 user} other {{count} users}}`, |
93 | $localize`Ban ${this.usersToBan.length} users` | 93 | { count: this.usersToBan.length } |
94 | ) | 94 | ) |
95 | } | 95 | } |
96 | 96 | ||
diff --git a/client/src/app/shared/shared-moderation/video-block.component.ts b/client/src/app/shared/shared-moderation/video-block.component.ts index 3ff53443a..0137def89 100644 --- a/client/src/app/shared/shared-moderation/video-block.component.ts +++ b/client/src/app/shared/shared-moderation/video-block.component.ts | |||
@@ -1,6 +1,6 @@ | |||
1 | import { Component, EventEmitter, OnInit, Output, ViewChild } from '@angular/core' | 1 | import { Component, EventEmitter, OnInit, Output, ViewChild } from '@angular/core' |
2 | import { Notifier } from '@app/core' | 2 | import { Notifier } from '@app/core' |
3 | import { prepareIcu } from '@app/helpers' | 3 | import { formatICU } from '@app/helpers' |
4 | import { FormReactive, FormReactiveService } from '@app/shared/shared-forms' | 4 | import { FormReactive, FormReactiveService } from '@app/shared/shared-forms' |
5 | import { Video } from '@app/shared/shared-main' | 5 | import { Video } from '@app/shared/shared-main' |
6 | import { NgbModal } from '@ng-bootstrap/ng-bootstrap' | 6 | import { NgbModal } from '@ng-bootstrap/ng-bootstrap' |
@@ -81,9 +81,9 @@ export class VideoBlockComponent extends FormReactive implements OnInit { | |||
81 | this.videoBlocklistService.blockVideo(options) | 81 | this.videoBlocklistService.blockVideo(options) |
82 | .subscribe({ | 82 | .subscribe({ |
83 | next: () => { | 83 | next: () => { |
84 | const message = prepareIcu($localize`{count, plural, =1 {Blocked {videoName}.} other {Blocked {count} videos.}}`)( | 84 | const message = formatICU( |
85 | { count: this.videos.length, videoName: this.getSingleVideo().name }, | 85 | $localize`{count, plural, =1 {Blocked {videoName}.} other {Blocked {count} videos.}}`, |
86 | $localize`Blocked ${this.videos.length} videos.` | 86 | { count: this.videos.length, videoName: this.getSingleVideo().name } |
87 | ) | 87 | ) |
88 | 88 | ||
89 | this.notifier.success(message) | 89 | this.notifier.success(message) |
diff --git a/client/tsconfig.json b/client/tsconfig.json index 785ed1c6c..f6409402a 100644 --- a/client/tsconfig.json +++ b/client/tsconfig.json | |||
@@ -89,8 +89,7 @@ | |||
89 | ], | 89 | ], |
90 | "exclude": [ | 90 | "exclude": [ |
91 | "../node_modules", | 91 | "../node_modules", |
92 | "../server", | 92 | "../server" |
93 | "node_modules" | ||
94 | ], | 93 | ], |
95 | "angularCompilerOptions": { | 94 | "angularCompilerOptions": { |
96 | "strictInjectionParameters": true, | 95 | "strictInjectionParameters": true, |
diff --git a/client/yarn.lock b/client/yarn.lock index aeb13a7b5..282c851ec 100644 --- a/client/yarn.lock +++ b/client/yarn.lock | |||
@@ -1647,6 +1647,14 @@ | |||
1647 | "@formatjs/intl-localematcher" "0.2.32" | 1647 | "@formatjs/intl-localematcher" "0.2.32" |
1648 | tslib "^2.4.0" | 1648 | tslib "^2.4.0" |
1649 | 1649 | ||
1650 | "@formatjs/ecma402-abstract@1.16.0": | ||
1651 | version "1.16.0" | ||
1652 | resolved "https://registry.yarnpkg.com/@formatjs/ecma402-abstract/-/ecma402-abstract-1.16.0.tgz#15a0baa8401880d4010eb93440d996e896ca251c" | ||
1653 | integrity sha512-qIH2cmG/oHGrVdApbqDf6/YR+B2A4NdkBjKLeq369OMVkqMFsC5oPSP1xpiyL1cAn+PbNEZHxwOVMYD/C76c6g== | ||
1654 | dependencies: | ||
1655 | "@formatjs/intl-localematcher" "0.3.0" | ||
1656 | tslib "^2.4.0" | ||
1657 | |||
1650 | "@formatjs/fast-memoize@2.0.1": | 1658 | "@formatjs/fast-memoize@2.0.1": |
1651 | version "2.0.1" | 1659 | version "2.0.1" |
1652 | resolved "https://registry.yarnpkg.com/@formatjs/fast-memoize/-/fast-memoize-2.0.1.tgz#f15aaa73caad5562899c69bdcad8db82adcd3b0b" | 1660 | resolved "https://registry.yarnpkg.com/@formatjs/fast-memoize/-/fast-memoize-2.0.1.tgz#f15aaa73caad5562899c69bdcad8db82adcd3b0b" |
@@ -1671,6 +1679,30 @@ | |||
1671 | "@formatjs/ecma402-abstract" "1.15.0" | 1679 | "@formatjs/ecma402-abstract" "1.15.0" |
1672 | tslib "^2.4.0" | 1680 | tslib "^2.4.0" |
1673 | 1681 | ||
1682 | "@formatjs/intl-enumerator@1.3.1": | ||
1683 | version "1.3.1" | ||
1684 | resolved "https://registry.yarnpkg.com/@formatjs/intl-enumerator/-/intl-enumerator-1.3.1.tgz#32f0a3b5aece244977aad16fe1cd5c205defc7f8" | ||
1685 | integrity sha512-UMuD1tNRb8JC+mZo0KeuSntJNie0+TLxXl/1QxRIRMR7z2UuJgphrK/UTUibAx9hjywL1qGdNNhD6QX//pvNyA== | ||
1686 | dependencies: | ||
1687 | tslib "^2.4.0" | ||
1688 | |||
1689 | "@formatjs/intl-getcanonicallocales@2.2.1": | ||
1690 | version "2.2.1" | ||
1691 | resolved "https://registry.yarnpkg.com/@formatjs/intl-getcanonicallocales/-/intl-getcanonicallocales-2.2.1.tgz#0d70251b16bec06c1c4a40b37892ea5b51e3a2bb" | ||
1692 | integrity sha512-KooqmyY+Mhq3ioASPzoU6p6Cy9Mx+cWSVQSP6lF+vEW2tiaN90ti08cp82p1dzFschenduOYgPKrNcBpsDi6+g== | ||
1693 | dependencies: | ||
1694 | tslib "^2.4.0" | ||
1695 | |||
1696 | "@formatjs/intl-locale@^3.3.1": | ||
1697 | version "3.3.1" | ||
1698 | resolved "https://registry.yarnpkg.com/@formatjs/intl-locale/-/intl-locale-3.3.1.tgz#2b86e85319913e0bedfcd64884be33bc4bcef73e" | ||
1699 | integrity sha512-Rg3BLIjMzVxBcZCsPhvwIrcfc+UVEzPZPKnvoOLh6KNNWrzWnRo0ORQEE/KRDyvYZxAmALV/GHCcRl+qlchKuw== | ||
1700 | dependencies: | ||
1701 | "@formatjs/ecma402-abstract" "1.16.0" | ||
1702 | "@formatjs/intl-enumerator" "1.3.1" | ||
1703 | "@formatjs/intl-getcanonicallocales" "2.2.1" | ||
1704 | tslib "^2.4.0" | ||
1705 | |||
1674 | "@formatjs/intl-localematcher@0.2.32": | 1706 | "@formatjs/intl-localematcher@0.2.32": |
1675 | version "0.2.32" | 1707 | version "0.2.32" |
1676 | resolved "https://registry.yarnpkg.com/@formatjs/intl-localematcher/-/intl-localematcher-0.2.32.tgz#00d4d307cd7d514b298e15a11a369b86c8933ec1" | 1708 | resolved "https://registry.yarnpkg.com/@formatjs/intl-localematcher/-/intl-localematcher-0.2.32.tgz#00d4d307cd7d514b298e15a11a369b86c8933ec1" |
@@ -1678,6 +1710,22 @@ | |||
1678 | dependencies: | 1710 | dependencies: |
1679 | tslib "^2.4.0" | 1711 | tslib "^2.4.0" |
1680 | 1712 | ||
1713 | "@formatjs/intl-localematcher@0.3.0": | ||
1714 | version "0.3.0" | ||
1715 | resolved "https://registry.yarnpkg.com/@formatjs/intl-localematcher/-/intl-localematcher-0.3.0.tgz#9ad570d90d302b60bcbe78efd5fcd7593c440579" | ||
1716 | integrity sha512-NFoxXX3dtZ6B53NlCErq181NxN/noMZOWKHfcEPQRNfV0a19THxyjxu2RTSNS3532wGm6fOdid5qsBQWg0Rhtw== | ||
1717 | dependencies: | ||
1718 | tslib "^2.4.0" | ||
1719 | |||
1720 | "@formatjs/intl-pluralrules@^5.2.2": | ||
1721 | version "5.2.2" | ||
1722 | resolved "https://registry.yarnpkg.com/@formatjs/intl-pluralrules/-/intl-pluralrules-5.2.2.tgz#6322d20a6d0172459e4faf4b0f06603c931673aa" | ||
1723 | integrity sha512-mEbnbRzsSCIYqaBmrmUlOsPu5MG6KfMcnzekPzUrUucX2dNiI1KWBGHK6IoXl5c8zx60L1NXJ6cSQ7akoc15SQ== | ||
1724 | dependencies: | ||
1725 | "@formatjs/ecma402-abstract" "1.15.0" | ||
1726 | "@formatjs/intl-localematcher" "0.2.32" | ||
1727 | tslib "^2.4.0" | ||
1728 | |||
1681 | "@gar/promisify@^1.1.3": | 1729 | "@gar/promisify@^1.1.3": |
1682 | version "1.1.3" | 1730 | version "1.1.3" |
1683 | resolved "https://registry.yarnpkg.com/@gar/promisify/-/promisify-1.1.3.tgz#555193ab2e3bb3b6adc3d551c9c030d9e860daf6" | 1731 | resolved "https://registry.yarnpkg.com/@gar/promisify/-/promisify-1.1.3.tgz#555193ab2e3bb3b6adc3d551c9c030d9e860daf6" |