diff options
-rw-r--r-- | client/src/app/app.component.html | 4 | ||||
-rw-r--r-- | client/src/app/app.component.scss | 11 | ||||
-rw-r--r-- | client/src/app/app.module.ts | 2 | ||||
-rw-r--r-- | client/src/app/menu/language-chooser.component.html | 15 | ||||
-rw-r--r-- | client/src/app/menu/language-chooser.component.scss | 15 | ||||
-rw-r--r-- | client/src/app/menu/language-chooser.component.ts | 32 | ||||
-rw-r--r-- | client/src/app/menu/menu.component.html | 126 | ||||
-rw-r--r-- | client/src/app/menu/menu.component.scss | 47 | ||||
-rw-r--r-- | client/src/app/menu/menu.component.ts | 10 | ||||
-rw-r--r-- | client/src/assets/images/menu/language.png | bin | 0 -> 10937 bytes | |||
-rw-r--r-- | client/src/sass/application.scss | 2 | ||||
-rw-r--r-- | client/src/sass/include/_variables.scss | 2 | ||||
-rw-r--r-- | package.json | 1 | ||||
-rw-r--r-- | server.ts | 3 | ||||
-rw-r--r-- | server/controllers/client.ts | 37 | ||||
-rw-r--r-- | shared/models/i18n/i18n.ts | 7 | ||||
-rw-r--r-- | yarn.lock | 7 |
17 files changed, 231 insertions, 90 deletions
diff --git a/client/src/app/app.component.html b/client/src/app/app.component.html index e50546633..09b2c15be 100644 --- a/client/src/app/app.component.html +++ b/client/src/app/app.component.html | |||
@@ -18,9 +18,7 @@ | |||
18 | </div> | 18 | </div> |
19 | 19 | ||
20 | <div class="sub-header-container"> | 20 | <div class="sub-header-container"> |
21 | <div *ngIf="isMenuDisplayed" class="title-menu-left"> | 21 | <my-menu *ngIf="isMenuDisplayed"></my-menu> |
22 | <my-menu></my-menu> | ||
23 | </div> | ||
24 | 22 | ||
25 | <div class="main-col container-fluid" [ngClass]="{ expanded: isMenuDisplayed === false }"> | 23 | <div class="main-col container-fluid" [ngClass]="{ expanded: isMenuDisplayed === false }"> |
26 | 24 | ||
diff --git a/client/src/app/app.component.scss b/client/src/app/app.component.scss index 6edf966f9..9eca31320 100644 --- a/client/src/app/app.component.scss +++ b/client/src/app/app.component.scss | |||
@@ -9,17 +9,6 @@ | |||
9 | margin-top: $header-height; | 9 | margin-top: $header-height; |
10 | } | 10 | } |
11 | 11 | ||
12 | .title-menu-left { | ||
13 | position: fixed; | ||
14 | height: calc(100vh - #{$header-height}); | ||
15 | padding: 0; | ||
16 | width: $menu-width; | ||
17 | |||
18 | .title-menu-left-block.menu { | ||
19 | height: 100%; | ||
20 | } | ||
21 | } | ||
22 | |||
23 | .header { | 12 | .header { |
24 | height: $header-height; | 13 | height: $header-height; |
25 | position: fixed; | 14 | position: fixed; |
diff --git a/client/src/app/app.module.ts b/client/src/app/app.module.ts index 9cffdd31e..48886fd4e 100644 --- a/client/src/app/app.module.ts +++ b/client/src/app/app.module.ts | |||
@@ -17,6 +17,7 @@ import { SignupModule } from './signup' | |||
17 | import { VideosModule } from './videos' | 17 | import { VideosModule } from './videos' |
18 | import { buildFileLocale, getCompleteLocale, isDefaultLocale } from '../../../shared/models/i18n' | 18 | import { buildFileLocale, getCompleteLocale, isDefaultLocale } from '../../../shared/models/i18n' |
19 | import { getDevLocale, isOnDevLocale } from '@app/shared/i18n/i18n-utils' | 19 | import { getDevLocale, isOnDevLocale } from '@app/shared/i18n/i18n-utils' |
20 | import { LanguageChooserComponent } from '@app/menu/language-chooser.component' | ||
20 | 21 | ||
21 | export function metaFactory (serverService: ServerService): MetaLoader { | 22 | export function metaFactory (serverService: ServerService): MetaLoader { |
22 | return new MetaStaticLoader({ | 23 | return new MetaStaticLoader({ |
@@ -36,6 +37,7 @@ export function metaFactory (serverService: ServerService): MetaLoader { | |||
36 | AppComponent, | 37 | AppComponent, |
37 | 38 | ||
38 | MenuComponent, | 39 | MenuComponent, |
40 | LanguageChooserComponent, | ||
39 | HeaderComponent | 41 | HeaderComponent |
40 | ], | 42 | ], |
41 | imports: [ | 43 | imports: [ |
diff --git a/client/src/app/menu/language-chooser.component.html b/client/src/app/menu/language-chooser.component.html new file mode 100644 index 000000000..f941e32f8 --- /dev/null +++ b/client/src/app/menu/language-chooser.component.html | |||
@@ -0,0 +1,15 @@ | |||
1 | <div bsModal #modal="bs-modal" class="modal" tabindex="-1"> | ||
2 | <div class="modal-dialog"> | ||
3 | <div class="modal-content"> | ||
4 | |||
5 | <div class="modal-header"> | ||
6 | <span class="close" aria-hidden="true" (click)="hide()"></span> | ||
7 | <h4 i18n class="modal-title">Change the language</h4> | ||
8 | </div> | ||
9 | |||
10 | <div class="modal-body" *ngFor="let lang of languages"> | ||
11 | <a [href]="buildLanguageLink(lang)">{{ lang.label }}</a> | ||
12 | </div> | ||
13 | </div> | ||
14 | </div> | ||
15 | </div> | ||
diff --git a/client/src/app/menu/language-chooser.component.scss b/client/src/app/menu/language-chooser.component.scss new file mode 100644 index 000000000..4574f78c6 --- /dev/null +++ b/client/src/app/menu/language-chooser.component.scss | |||
@@ -0,0 +1,15 @@ | |||
1 | @import '_variables'; | ||
2 | @import '_mixins'; | ||
3 | |||
4 | .modal-title { | ||
5 | text-align: center; | ||
6 | } | ||
7 | |||
8 | .modal-body { | ||
9 | text-align: center; | ||
10 | |||
11 | a { | ||
12 | font-size: 16px; | ||
13 | margin-top: 10px; | ||
14 | } | ||
15 | } \ No newline at end of file | ||
diff --git a/client/src/app/menu/language-chooser.component.ts b/client/src/app/menu/language-chooser.component.ts new file mode 100644 index 000000000..3de6a129d --- /dev/null +++ b/client/src/app/menu/language-chooser.component.ts | |||
@@ -0,0 +1,32 @@ | |||
1 | import { Component, ViewChild } from '@angular/core' | ||
2 | import { ModalDirective } from 'ngx-bootstrap/modal' | ||
3 | import { I18N_LOCALES } from '../../../../shared' | ||
4 | |||
5 | @Component({ | ||
6 | selector: 'my-language-chooser', | ||
7 | templateUrl: './language-chooser.component.html', | ||
8 | styleUrls: [ './language-chooser.component.scss' ] | ||
9 | }) | ||
10 | export class LanguageChooserComponent { | ||
11 | @ViewChild('modal') modal: ModalDirective | ||
12 | |||
13 | languages: { [ id: string ]: string }[] = [] | ||
14 | |||
15 | constructor () { | ||
16 | this.languages = Object.keys(I18N_LOCALES) | ||
17 | .map(k => ({ id: k, label: I18N_LOCALES[k] })) | ||
18 | } | ||
19 | |||
20 | show () { | ||
21 | this.modal.show() | ||
22 | } | ||
23 | |||
24 | hide () { | ||
25 | this.modal.hide() | ||
26 | } | ||
27 | |||
28 | buildLanguageLink (lang: { id: string }) { | ||
29 | return window.location.origin + '/' + lang.id | ||
30 | } | ||
31 | |||
32 | } | ||
diff --git a/client/src/app/menu/menu.component.html b/client/src/app/menu/menu.component.html index 8e3b295f7..784b5cd85 100644 --- a/client/src/app/menu/menu.component.html +++ b/client/src/app/menu/menu.component.html | |||
@@ -1,70 +1,82 @@ | |||
1 | <menu> | 1 | <div class="menu-wrapper"> |
2 | <div *ngIf="isLoggedIn" class="logged-in-block"> | 2 | <menu> |
3 | <a routerLink="/my-account/settings"> | 3 | <div class="top-menu"> |
4 | <img [src]="user.accountAvatarUrl" alt="Avatar" /> | 4 | <div *ngIf="isLoggedIn" class="logged-in-block"> |
5 | </a> | 5 | <a routerLink="/my-account/settings"> |
6 | <img [src]="user.accountAvatarUrl" alt="Avatar" /> | ||
7 | </a> | ||
6 | 8 | ||
7 | <div class="logged-in-info"> | 9 | <div class="logged-in-info"> |
8 | <a routerLink="/my-account/settings" class="logged-in-username">{{ user.account?.displayName }}</a> | 10 | <a routerLink="/my-account/settings" class="logged-in-username">{{ user.account?.displayName }}</a> |
9 | <div class="logged-in-email">{{ user.email }}</div> | 11 | <div class="logged-in-email">{{ user.email }}</div> |
10 | </div> | 12 | </div> |
11 | 13 | ||
12 | <div class="logged-in-more" dropdown placement="right" container="body"> | 14 | <div class="logged-in-more" dropdown placement="right" container="body"> |
13 | <span class="glyphicon glyphicon-option-vertical" dropdownToggle></span> | 15 | <span class="glyphicon glyphicon-option-vertical" dropdownToggle></span> |
14 | 16 | ||
15 | <ul *dropdownMenu class="dropdown-menu"> | 17 | <ul *dropdownMenu class="dropdown-menu"> |
16 | <li> | 18 | <li> |
17 | <a i18n [routerLink]="[ '/accounts', user.account?.nameWithHost ]" class="dropdown-item" title="My public profile"> | 19 | <a i18n [routerLink]="[ '/accounts', user.account?.nameWithHost ]" class="dropdown-item" title="My public profile"> |
18 | My public profile | 20 | My public profile |
19 | </a> | 21 | </a> |
20 | 22 | ||
21 | <a i18n routerLink="/my-account" class="dropdown-item" title="My account"> | 23 | <a i18n routerLink="/my-account" class="dropdown-item" title="My account"> |
22 | My account | 24 | My account |
23 | </a> | 25 | </a> |
24 | 26 | ||
25 | <a i18n (click)="logout($event)" class="dropdown-item" title="Log out" href="#"> | 27 | <a i18n (click)="logout($event)" class="dropdown-item" title="Log out" href="#"> |
26 | Log out | 28 | Log out |
27 | </a> | 29 | </a> |
28 | </li> | 30 | </li> |
29 | </ul> | 31 | </ul> |
30 | </div> | 32 | </div> |
31 | </div> | 33 | </div> |
34 | |||
35 | <div *ngIf="!isLoggedIn" class="button-block"> | ||
36 | <a i18n routerLink="/login" class="login-button">Login</a> | ||
37 | <a i18n *ngIf="isRegistrationAllowed()" routerLink="/signup" class="create-account-button">Create an account</a> | ||
38 | </div> | ||
32 | 39 | ||
33 | <div *ngIf="!isLoggedIn" class="button-block"> | 40 | <div class="panel-block"> |
34 | <a i18n routerLink="/login" class="login-button">Login</a> | 41 | <div i18n class="block-title">Videos</div> |
35 | <a i18n *ngIf="isRegistrationAllowed()" routerLink="/signup" class="create-account-button">Create an account</a> | ||
36 | </div> | ||
37 | 42 | ||
38 | <div class="panel-block"> | 43 | <a routerLink="/videos/trending" routerLinkActive="active"> |
39 | <div i18n class="block-title">Videos</div> | 44 | <span class="icon icon-videos-trending"></span> |
45 | <ng-container i18n>Trending</ng-container> | ||
46 | </a> | ||
40 | 47 | ||
41 | <a routerLink="/videos/trending" routerLinkActive="active"> | 48 | <a routerLink="/videos/recently-added" routerLinkActive="active"> |
42 | <span class="icon icon-videos-trending"></span> | 49 | <span class="icon icon-videos-recently-added"></span> |
43 | <ng-container i18n>Trending</ng-container> | 50 | <ng-container i18n>Recently added</ng-container> |
44 | </a> | 51 | </a> |
45 | 52 | ||
46 | <a routerLink="/videos/recently-added" routerLinkActive="active"> | 53 | <a routerLink="/videos/local" routerLinkActive="active"> |
47 | <span class="icon icon-videos-recently-added"></span> | 54 | <span class="icon icon-videos-local"></span> |
48 | <ng-container i18n>Recently added</ng-container> | 55 | <ng-container i18n>Local</ng-container> |
49 | </a> | 56 | </a> |
57 | </div> | ||
50 | 58 | ||
51 | <a routerLink="/videos/local" routerLinkActive="active"> | 59 | <div class="panel-block"> |
52 | <span class="icon icon-videos-local"></span> | 60 | <div class="block-title">More</div> |
53 | <ng-container i18n>Local</ng-container> | ||
54 | </a> | ||
55 | </div> | ||
56 | 61 | ||
57 | <div class="panel-block"> | 62 | <a *ngIf="userHasAdminAccess" [routerLink]="getFirstAdminRouteAvailable()" routerLinkActive="active"> |
58 | <div class="block-title">More</div> | 63 | <span class="icon icon-administration"></span> |
64 | <ng-container i18n>Administration</ng-container> | ||
65 | </a> | ||
59 | 66 | ||
60 | <a *ngIf="userHasAdminAccess" [routerLink]="getFirstAdminRouteAvailable()" routerLinkActive="active"> | 67 | <a routerLink="/about" routerLinkActive="active"> |
61 | <span class="icon icon-administration"></span> | 68 | <span class="icon icon-about"></span> |
62 | <ng-container i18n>Administration</ng-container> | 69 | <ng-container i18n>About</ng-container> |
63 | </a> | 70 | </a> |
71 | </div> | ||
72 | </div> | ||
73 | |||
74 | <div class="footer"> | ||
75 | <span class="language"> | ||
76 | <span (click)="openLanguageChooser()" i18n-title title="Change the language" class="icon icon-language"></span> | ||
77 | </span> | ||
78 | </div> | ||
79 | </menu> | ||
80 | </div> | ||
64 | 81 | ||
65 | <a routerLink="/about" routerLinkActive="active"> | 82 | <my-language-chooser #languageChooserModal></my-language-chooser> \ No newline at end of file |
66 | <span class="icon icon-about"></span> | ||
67 | <ng-container i18n>About</ng-container> | ||
68 | </a> | ||
69 | </div> | ||
70 | </menu> | ||
diff --git a/client/src/app/menu/menu.component.scss b/client/src/app/menu/menu.component.scss index c36a7aa36..e61f4acd3 100644 --- a/client/src/app/menu/menu.component.scss +++ b/client/src/app/menu/menu.component.scss | |||
@@ -1,6 +1,13 @@ | |||
1 | @import '_variables'; | 1 | @import '_variables'; |
2 | @import '_mixins'; | 2 | @import '_mixins'; |
3 | 3 | ||
4 | .menu-wrapper { | ||
5 | position: fixed; | ||
6 | height: calc(100vh - #{$header-height}); | ||
7 | padding: 0; | ||
8 | width: $menu-width; | ||
9 | } | ||
10 | |||
4 | menu { | 11 | menu { |
5 | background-color: $black-background; | 12 | background-color: $black-background; |
6 | margin: 0; | 13 | margin: 0; |
@@ -11,6 +18,13 @@ menu { | |||
11 | overflow: hidden; | 18 | overflow: hidden; |
12 | z-index: 1000; | 19 | z-index: 1000; |
13 | color: $menu-color; | 20 | color: $menu-color; |
21 | overflow-y: auto; | ||
22 | display: flex; | ||
23 | flex-direction: column; | ||
24 | |||
25 | .top-menu { | ||
26 | flex-grow: 1; | ||
27 | } | ||
14 | 28 | ||
15 | .logged-in-block { | 29 | .logged-in-block { |
16 | height: 100px; | 30 | height: 100px; |
@@ -100,7 +114,7 @@ menu { | |||
100 | a { | 114 | a { |
101 | display: flex; | 115 | display: flex; |
102 | align-items: center; | 116 | align-items: center; |
103 | padding-left: 26px; | 117 | padding-left: $menu-left-padding; |
104 | color: $menu-color; | 118 | color: $menu-color; |
105 | cursor: pointer; | 119 | cursor: pointer; |
106 | height: 40px; | 120 | height: 40px; |
@@ -155,4 +169,35 @@ menu { | |||
155 | } | 169 | } |
156 | } | 170 | } |
157 | } | 171 | } |
172 | |||
173 | .footer { | ||
174 | margin-bottom: 15px; | ||
175 | padding-left: $menu-left-padding; | ||
176 | |||
177 | .language { | ||
178 | display: inline-block; | ||
179 | color: $menu-bottom-color; | ||
180 | cursor: pointer; | ||
181 | font-size: 12px; | ||
182 | font-weight: $font-semibold; | ||
183 | |||
184 | .icon { | ||
185 | @include icon(28px); | ||
186 | opacity: 0.9; | ||
187 | |||
188 | &.icon-language { | ||
189 | position: relative; | ||
190 | top: -1px; | ||
191 | width: 28px; | ||
192 | height: 24px; | ||
193 | |||
194 | background-image: url('../../assets/images/menu/language.png'); | ||
195 | } | ||
196 | |||
197 | &:hover { | ||
198 | opacity: 1; | ||
199 | } | ||
200 | } | ||
201 | } | ||
202 | } | ||
158 | } | 203 | } |
diff --git a/client/src/app/menu/menu.component.ts b/client/src/app/menu/menu.component.ts index c0aea89b3..dded6b4d5 100644 --- a/client/src/app/menu/menu.component.ts +++ b/client/src/app/menu/menu.component.ts | |||
@@ -1,8 +1,8 @@ | |||
1 | import { Component, OnInit } from '@angular/core' | 1 | import { Component, OnInit, ViewChild } from '@angular/core' |
2 | import { Router } from '@angular/router' | ||
3 | import { UserRight } from '../../../../shared/models/users/user-right.enum' | 2 | import { UserRight } from '../../../../shared/models/users/user-right.enum' |
4 | import { AuthService, AuthStatus, RedirectService, ServerService } from '../core' | 3 | import { AuthService, AuthStatus, RedirectService, ServerService } from '../core' |
5 | import { User } from '../shared/users/user.model' | 4 | import { User } from '../shared/users/user.model' |
5 | import { LanguageChooserComponent } from '@app/menu/language-chooser.component' | ||
6 | 6 | ||
7 | @Component({ | 7 | @Component({ |
8 | selector: 'my-menu', | 8 | selector: 'my-menu', |
@@ -10,6 +10,8 @@ import { User } from '../shared/users/user.model' | |||
10 | styleUrls: [ './menu.component.scss' ] | 10 | styleUrls: [ './menu.component.scss' ] |
11 | }) | 11 | }) |
12 | export class MenuComponent implements OnInit { | 12 | export class MenuComponent implements OnInit { |
13 | @ViewChild('languageChooserModal') languageChooserModal: LanguageChooserComponent | ||
14 | |||
13 | user: User | 15 | user: User |
14 | isLoggedIn: boolean | 16 | isLoggedIn: boolean |
15 | userHasAdminAccess = false | 17 | userHasAdminAccess = false |
@@ -90,6 +92,10 @@ export class MenuComponent implements OnInit { | |||
90 | this.redirectService.redirectToHomepage() | 92 | this.redirectService.redirectToHomepage() |
91 | } | 93 | } |
92 | 94 | ||
95 | openLanguageChooser () { | ||
96 | this.languageChooserModal.show() | ||
97 | } | ||
98 | |||
93 | private computeIsUserHasAdminAccess () { | 99 | private computeIsUserHasAdminAccess () { |
94 | const right = this.getFirstAdminRightAvailable() | 100 | const right = this.getFirstAdminRightAvailable() |
95 | 101 | ||
diff --git a/client/src/assets/images/menu/language.png b/client/src/assets/images/menu/language.png new file mode 100644 index 000000000..60e6fec00 --- /dev/null +++ b/client/src/assets/images/menu/language.png | |||
Binary files differ | |||
diff --git a/client/src/sass/application.scss b/client/src/sass/application.scss index dae0c52c2..96602dc38 100644 --- a/client/src/sass/application.scss +++ b/client/src/sass/application.scss | |||
@@ -288,7 +288,7 @@ table { | |||
288 | 288 | ||
289 | // On small screen, menu is absolute | 289 | // On small screen, menu is absolute |
290 | @media screen and (max-width: 600px) { | 290 | @media screen and (max-width: 600px) { |
291 | .title-menu-left { | 291 | .menu-wrapper { |
292 | width: 100% !important; | 292 | width: 100% !important; |
293 | position: absolute !important; | 293 | position: absolute !important; |
294 | z-index: 10000; | 294 | z-index: 10000; |
diff --git a/client/src/sass/include/_variables.scss b/client/src/sass/include/_variables.scss index 092f8ed24..f1f755126 100644 --- a/client/src/sass/include/_variables.scss +++ b/client/src/sass/include/_variables.scss | |||
@@ -22,7 +22,9 @@ $header-border-color: #e9eff6; | |||
22 | $search-input-width: 375px; | 22 | $search-input-width: 375px; |
23 | 23 | ||
24 | $menu-color: #fff; | 24 | $menu-color: #fff; |
25 | $menu-bottom-color: #C6C6C6; | ||
25 | $menu-width: 240px; | 26 | $menu-width: 240px; |
27 | $menu-left-padding: 26px; | ||
26 | 28 | ||
27 | $footer-height: 30px; | 29 | $footer-height: 30px; |
28 | $footer-margin: 30px; | 30 | $footer-margin: 30px; |
diff --git a/package.json b/package.json index edb14ff59..254281df5 100644 --- a/package.json +++ b/package.json | |||
@@ -84,6 +84,7 @@ | |||
84 | "commander": "^2.13.0", | 84 | "commander": "^2.13.0", |
85 | "concurrently": "^3.5.1", | 85 | "concurrently": "^3.5.1", |
86 | "config": "^1.14.0", | 86 | "config": "^1.14.0", |
87 | "cookie-parser": "^1.4.3", | ||
87 | "cors": "^2.8.1", | 88 | "cors": "^2.8.1", |
88 | "create-torrent": "^3.24.5", | 89 | "create-torrent": "^3.24.5", |
89 | "express": "^4.12.4", | 90 | "express": "^4.12.4", |
@@ -12,6 +12,7 @@ import * as bodyParser from 'body-parser' | |||
12 | import * as express from 'express' | 12 | import * as express from 'express' |
13 | import * as morgan from 'morgan' | 13 | import * as morgan from 'morgan' |
14 | import * as cors from 'cors' | 14 | import * as cors from 'cors' |
15 | import * as cookieParser from 'cookie-parser' | ||
15 | 16 | ||
16 | process.title = 'peertube' | 17 | process.title = 'peertube' |
17 | 18 | ||
@@ -112,6 +113,8 @@ app.use(bodyParser.json({ | |||
112 | type: [ 'application/json', 'application/*+json' ], | 113 | type: [ 'application/json', 'application/*+json' ], |
113 | limit: '500kb' | 114 | limit: '500kb' |
114 | })) | 115 | })) |
116 | // Cookies | ||
117 | app.use(cookieParser()) | ||
115 | 118 | ||
116 | // ----------- Views, routes and static files ----------- | 119 | // ----------- Views, routes and static files ----------- |
117 | 120 | ||
diff --git a/server/controllers/client.ts b/server/controllers/client.ts index 385757fa6..dfffe5487 100644 --- a/server/controllers/client.ts +++ b/server/controllers/client.ts | |||
@@ -7,8 +7,14 @@ import { ACCEPT_HEADERS, CONFIG, EMBED_SIZE, OPENGRAPH_AND_OEMBED_COMMENT, STATI | |||
7 | import { asyncMiddleware } from '../middlewares' | 7 | import { asyncMiddleware } from '../middlewares' |
8 | import { VideoModel } from '../models/video/video' | 8 | import { VideoModel } from '../models/video/video' |
9 | import { VideoPrivacy } from '../../shared/models/videos' | 9 | import { VideoPrivacy } from '../../shared/models/videos' |
10 | import { buildFileLocale, getCompleteLocale, getDefaultLocale, is18nLocale } from '../../shared/models' | 10 | import { |
11 | import { LOCALE_FILES } from '../../shared/models/i18n/i18n' | 11 | buildFileLocale, |
12 | getCompleteLocale, | ||
13 | getDefaultLocale, | ||
14 | is18nLocale, | ||
15 | LOCALE_FILES, | ||
16 | POSSIBLE_LOCALES | ||
17 | } from '../../shared/models/i18n/i18n' | ||
12 | 18 | ||
13 | const clientsRouter = express.Router() | 19 | const clientsRouter = express.Router() |
14 | 20 | ||
@@ -22,7 +28,8 @@ clientsRouter.use('/videos/watch/:id', | |||
22 | asyncMiddleware(generateWatchHtmlPage) | 28 | asyncMiddleware(generateWatchHtmlPage) |
23 | ) | 29 | ) |
24 | 30 | ||
25 | clientsRouter.use('/videos/embed', (req: express.Request, res: express.Response, next: express.NextFunction) => { | 31 | clientsRouter.use('' + |
32 | '/videos/embed', (req: express.Request, res: express.Response, next: express.NextFunction) => { | ||
26 | res.sendFile(embedPath) | 33 | res.sendFile(embedPath) |
27 | }) | 34 | }) |
28 | 35 | ||
@@ -63,7 +70,7 @@ clientsRouter.use('/client/*', (req: express.Request, res: express.Response, nex | |||
63 | // Try to provide the right language index.html | 70 | // Try to provide the right language index.html |
64 | clientsRouter.use('/(:language)?', function (req, res) { | 71 | clientsRouter.use('/(:language)?', function (req, res) { |
65 | if (req.accepts(ACCEPT_HEADERS) === 'html') { | 72 | if (req.accepts(ACCEPT_HEADERS) === 'html') { |
66 | return res.sendFile(getIndexPath(req, req.params.language)) | 73 | return res.sendFile(getIndexPath(req, res, req.params.language)) |
67 | } | 74 | } |
68 | 75 | ||
69 | return res.status(404).end() | 76 | return res.status(404).end() |
@@ -77,16 +84,24 @@ export { | |||
77 | 84 | ||
78 | // --------------------------------------------------------------------------- | 85 | // --------------------------------------------------------------------------- |
79 | 86 | ||
80 | function getIndexPath (req: express.Request, paramLang?: string) { | 87 | function getIndexPath (req: express.Request, res: express.Response, paramLang?: string) { |
81 | let lang: string | 88 | let lang: string |
82 | 89 | ||
83 | // Check param lang validity | 90 | // Check param lang validity |
84 | if (paramLang && is18nLocale(paramLang)) { | 91 | if (paramLang && is18nLocale(paramLang)) { |
85 | lang = paramLang | 92 | lang = paramLang |
93 | |||
94 | // Save locale in cookies | ||
95 | res.cookie('clientLanguage', lang, { | ||
96 | secure: CONFIG.WEBSERVER.SCHEME === 'https', | ||
97 | sameSite: true, | ||
98 | maxAge: 1000 * 3600 * 24 * 90 // 3 months | ||
99 | }) | ||
100 | |||
101 | } else if (req.cookies.clientLanguage && is18nLocale(req.cookies.clientLanguage)) { | ||
102 | lang = req.cookies.clientLanguage | ||
86 | } else { | 103 | } else { |
87 | // lang = req.acceptsLanguages(POSSIBLE_LOCALES) || getDefaultLocale() | 104 | lang = req.acceptsLanguages(POSSIBLE_LOCALES) || getDefaultLocale() |
88 | // Disable auto language for now | ||
89 | lang = getDefaultLocale() | ||
90 | } | 105 | } |
91 | 106 | ||
92 | return join(__dirname, '../../../client/dist/' + buildFileLocale(lang) + '/index.html') | 107 | return join(__dirname, '../../../client/dist/' + buildFileLocale(lang) + '/index.html') |
@@ -181,18 +196,18 @@ async function generateWatchHtmlPage (req: express.Request, res: express.Respons | |||
181 | } else if (validator.isInt(videoId)) { | 196 | } else if (validator.isInt(videoId)) { |
182 | videoPromise = VideoModel.loadAndPopulateAccountAndServerAndTags(+videoId) | 197 | videoPromise = VideoModel.loadAndPopulateAccountAndServerAndTags(+videoId) |
183 | } else { | 198 | } else { |
184 | return res.sendFile(getIndexPath(req)) | 199 | return res.sendFile(getIndexPath(req, res)) |
185 | } | 200 | } |
186 | 201 | ||
187 | let [ file, video ] = await Promise.all([ | 202 | let [ file, video ] = await Promise.all([ |
188 | readFileBufferPromise(getIndexPath(req)), | 203 | readFileBufferPromise(getIndexPath(req, res)), |
189 | videoPromise | 204 | videoPromise |
190 | ]) | 205 | ]) |
191 | 206 | ||
192 | const html = file.toString() | 207 | const html = file.toString() |
193 | 208 | ||
194 | // Let Angular application handle errors | 209 | // Let Angular application handle errors |
195 | if (!video || video.privacy === VideoPrivacy.PRIVATE) return res.sendFile(getIndexPath(req)) | 210 | if (!video || video.privacy === VideoPrivacy.PRIVATE) return res.sendFile(getIndexPath(req, res)) |
196 | 211 | ||
197 | const htmlStringPageWithTags = addOpenGraphAndOEmbedTags(html, video) | 212 | const htmlStringPageWithTags = addOpenGraphAndOEmbedTags(html, video) |
198 | res.set('Content-Type', 'text/html; charset=UTF-8').send(htmlStringPageWithTags) | 213 | res.set('Content-Type', 'text/html; charset=UTF-8').send(htmlStringPageWithTags) |
diff --git a/shared/models/i18n/i18n.ts b/shared/models/i18n/i18n.ts index e2b440900..14b02a01d 100644 --- a/shared/models/i18n/i18n.ts +++ b/shared/models/i18n/i18n.ts | |||
@@ -1,8 +1,8 @@ | |||
1 | export const LOCALE_FILES = [ 'player', 'server' ] | 1 | export const LOCALE_FILES = [ 'player', 'server' ] |
2 | 2 | ||
3 | export const I18N_LOCALES = { | 3 | export const I18N_LOCALES = { |
4 | 'en-US': 'English (US)', | 4 | 'en-US': 'English', |
5 | 'fr-FR': 'Français (France)' | 5 | 'fr-FR': 'Français' |
6 | } | 6 | } |
7 | 7 | ||
8 | const I18N_LOCALE_ALIAS = { | 8 | const I18N_LOCALE_ALIAS = { |
@@ -13,8 +13,6 @@ const I18N_LOCALE_ALIAS = { | |||
13 | export const POSSIBLE_LOCALES = Object.keys(I18N_LOCALES) | 13 | export const POSSIBLE_LOCALES = Object.keys(I18N_LOCALES) |
14 | .concat(Object.keys(I18N_LOCALE_ALIAS)) | 14 | .concat(Object.keys(I18N_LOCALE_ALIAS)) |
15 | 15 | ||
16 | const possiblePaths = POSSIBLE_LOCALES.map(l => '/' + l) | ||
17 | |||
18 | export function getDefaultLocale () { | 16 | export function getDefaultLocale () { |
19 | return 'en-US' | 17 | return 'en-US' |
20 | } | 18 | } |
@@ -23,6 +21,7 @@ export function isDefaultLocale (locale: string) { | |||
23 | return getCompleteLocale(locale) === getCompleteLocale(getDefaultLocale()) | 21 | return getCompleteLocale(locale) === getCompleteLocale(getDefaultLocale()) |
24 | } | 22 | } |
25 | 23 | ||
24 | const possiblePaths = POSSIBLE_LOCALES.map(l => '/' + l) | ||
26 | export function is18nPath (path: string) { | 25 | export function is18nPath (path: string) { |
27 | return possiblePaths.indexOf(path) !== -1 | 26 | return possiblePaths.indexOf(path) !== -1 |
28 | } | 27 | } |
@@ -1590,6 +1590,13 @@ content-type@~1.0.1, content-type@~1.0.4: | |||
1590 | version "1.0.4" | 1590 | version "1.0.4" |
1591 | resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.4.tgz#e138cc75e040c727b1966fe5e5f8c9aee256fe3b" | 1591 | resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.4.tgz#e138cc75e040c727b1966fe5e5f8c9aee256fe3b" |
1592 | 1592 | ||
1593 | cookie-parser@^1.4.3: | ||
1594 | version "1.4.3" | ||
1595 | resolved "https://registry.yarnpkg.com/cookie-parser/-/cookie-parser-1.4.3.tgz#0fe31fa19d000b95f4aadf1f53fdc2b8a203baa5" | ||
1596 | dependencies: | ||
1597 | cookie "0.3.1" | ||
1598 | cookie-signature "1.0.6" | ||
1599 | |||
1593 | cookie-signature@1.0.6: | 1600 | cookie-signature@1.0.6: |
1594 | version "1.0.6" | 1601 | version "1.0.6" |
1595 | resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c" | 1602 | resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c" |