From 8afc19a6121569da054462c7cb351a3f13030a32 Mon Sep 17 00:00:00 2001 From: Chocobozzz <me@florianbigard.com> Date: Thu, 28 Jun 2018 13:59:48 +0200 Subject: [PATCH] Add ability to choose the language --- client/src/app/app.component.html | 4 +- client/src/app/app.component.scss | 11 -- client/src/app/app.module.ts | 2 + .../app/menu/language-chooser.component.html | 15 +++ .../app/menu/language-chooser.component.scss | 15 +++ .../app/menu/language-chooser.component.ts | 32 +++++ client/src/app/menu/menu.component.html | 126 ++++++++++-------- client/src/app/menu/menu.component.scss | 47 ++++++- client/src/app/menu/menu.component.ts | 10 +- client/src/assets/images/menu/language.png | Bin 0 -> 10937 bytes client/src/sass/application.scss | 2 +- client/src/sass/include/_variables.scss | 2 + package.json | 1 + server.ts | 3 + server/controllers/client.ts | 37 +++-- shared/models/i18n/i18n.ts | 7 +- yarn.lock | 7 + 17 files changed, 231 insertions(+), 90 deletions(-) create mode 100644 client/src/app/menu/language-chooser.component.html create mode 100644 client/src/app/menu/language-chooser.component.scss create mode 100644 client/src/app/menu/language-chooser.component.ts create mode 100644 client/src/assets/images/menu/language.png diff --git a/client/src/app/app.component.html b/client/src/app/app.component.html index e505466339..09b2c15be7 100644 --- a/client/src/app/app.component.html +++ b/client/src/app/app.component.html @@ -18,9 +18,7 @@ </div> <div class="sub-header-container"> - <div *ngIf="isMenuDisplayed" class="title-menu-left"> - <my-menu></my-menu> - </div> + <my-menu *ngIf="isMenuDisplayed"></my-menu> <div class="main-col container-fluid" [ngClass]="{ expanded: isMenuDisplayed === false }"> diff --git a/client/src/app/app.component.scss b/client/src/app/app.component.scss index 6edf966f99..9eca313203 100644 --- a/client/src/app/app.component.scss +++ b/client/src/app/app.component.scss @@ -9,17 +9,6 @@ margin-top: $header-height; } -.title-menu-left { - position: fixed; - height: calc(100vh - #{$header-height}); - padding: 0; - width: $menu-width; - - .title-menu-left-block.menu { - height: 100%; - } -} - .header { height: $header-height; position: fixed; diff --git a/client/src/app/app.module.ts b/client/src/app/app.module.ts index 9cffdd31e7..48886fd4e8 100644 --- a/client/src/app/app.module.ts +++ b/client/src/app/app.module.ts @@ -17,6 +17,7 @@ import { SignupModule } from './signup' import { VideosModule } from './videos' import { buildFileLocale, getCompleteLocale, isDefaultLocale } from '../../../shared/models/i18n' import { getDevLocale, isOnDevLocale } from '@app/shared/i18n/i18n-utils' +import { LanguageChooserComponent } from '@app/menu/language-chooser.component' export function metaFactory (serverService: ServerService): MetaLoader { return new MetaStaticLoader({ @@ -36,6 +37,7 @@ export function metaFactory (serverService: ServerService): MetaLoader { AppComponent, MenuComponent, + LanguageChooserComponent, HeaderComponent ], 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 0000000000..f941e32f81 --- /dev/null +++ b/client/src/app/menu/language-chooser.component.html @@ -0,0 +1,15 @@ +<div bsModal #modal="bs-modal" class="modal" tabindex="-1"> + <div class="modal-dialog"> + <div class="modal-content"> + + <div class="modal-header"> + <span class="close" aria-hidden="true" (click)="hide()"></span> + <h4 i18n class="modal-title">Change the language</h4> + </div> + + <div class="modal-body" *ngFor="let lang of languages"> + <a [href]="buildLanguageLink(lang)">{{ lang.label }}</a> + </div> + </div> + </div> +</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 0000000000..4574f78c69 --- /dev/null +++ b/client/src/app/menu/language-chooser.component.scss @@ -0,0 +1,15 @@ +@import '_variables'; +@import '_mixins'; + +.modal-title { + text-align: center; +} + +.modal-body { + text-align: center; + + a { + font-size: 16px; + margin-top: 10px; + } +} \ 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 0000000000..3de6a129d3 --- /dev/null +++ b/client/src/app/menu/language-chooser.component.ts @@ -0,0 +1,32 @@ +import { Component, ViewChild } from '@angular/core' +import { ModalDirective } from 'ngx-bootstrap/modal' +import { I18N_LOCALES } from '../../../../shared' + +@Component({ + selector: 'my-language-chooser', + templateUrl: './language-chooser.component.html', + styleUrls: [ './language-chooser.component.scss' ] +}) +export class LanguageChooserComponent { + @ViewChild('modal') modal: ModalDirective + + languages: { [ id: string ]: string }[] = [] + + constructor () { + this.languages = Object.keys(I18N_LOCALES) + .map(k => ({ id: k, label: I18N_LOCALES[k] })) + } + + show () { + this.modal.show() + } + + hide () { + this.modal.hide() + } + + buildLanguageLink (lang: { id: string }) { + return window.location.origin + '/' + lang.id + } + +} diff --git a/client/src/app/menu/menu.component.html b/client/src/app/menu/menu.component.html index 8e3b295f77..784b5cd857 100644 --- a/client/src/app/menu/menu.component.html +++ b/client/src/app/menu/menu.component.html @@ -1,70 +1,82 @@ -<menu> - <div *ngIf="isLoggedIn" class="logged-in-block"> - <a routerLink="/my-account/settings"> - <img [src]="user.accountAvatarUrl" alt="Avatar" /> - </a> +<div class="menu-wrapper"> + <menu> + <div class="top-menu"> + <div *ngIf="isLoggedIn" class="logged-in-block"> + <a routerLink="/my-account/settings"> + <img [src]="user.accountAvatarUrl" alt="Avatar" /> + </a> - <div class="logged-in-info"> - <a routerLink="/my-account/settings" class="logged-in-username">{{ user.account?.displayName }}</a> - <div class="logged-in-email">{{ user.email }}</div> - </div> + <div class="logged-in-info"> + <a routerLink="/my-account/settings" class="logged-in-username">{{ user.account?.displayName }}</a> + <div class="logged-in-email">{{ user.email }}</div> + </div> - <div class="logged-in-more" dropdown placement="right" container="body"> - <span class="glyphicon glyphicon-option-vertical" dropdownToggle></span> + <div class="logged-in-more" dropdown placement="right" container="body"> + <span class="glyphicon glyphicon-option-vertical" dropdownToggle></span> - <ul *dropdownMenu class="dropdown-menu"> - <li> - <a i18n [routerLink]="[ '/accounts', user.account?.nameWithHost ]" class="dropdown-item" title="My public profile"> - My public profile - </a> + <ul *dropdownMenu class="dropdown-menu"> + <li> + <a i18n [routerLink]="[ '/accounts', user.account?.nameWithHost ]" class="dropdown-item" title="My public profile"> + My public profile + </a> - <a i18n routerLink="/my-account" class="dropdown-item" title="My account"> - My account - </a> + <a i18n routerLink="/my-account" class="dropdown-item" title="My account"> + My account + </a> - <a i18n (click)="logout($event)" class="dropdown-item" title="Log out" href="#"> - Log out - </a> - </li> - </ul> - </div> - </div> + <a i18n (click)="logout($event)" class="dropdown-item" title="Log out" href="#"> + Log out + </a> + </li> + </ul> + </div> + </div> + + <div *ngIf="!isLoggedIn" class="button-block"> + <a i18n routerLink="/login" class="login-button">Login</a> + <a i18n *ngIf="isRegistrationAllowed()" routerLink="/signup" class="create-account-button">Create an account</a> + </div> - <div *ngIf="!isLoggedIn" class="button-block"> - <a i18n routerLink="/login" class="login-button">Login</a> - <a i18n *ngIf="isRegistrationAllowed()" routerLink="/signup" class="create-account-button">Create an account</a> - </div> + <div class="panel-block"> + <div i18n class="block-title">Videos</div> - <div class="panel-block"> - <div i18n class="block-title">Videos</div> + <a routerLink="/videos/trending" routerLinkActive="active"> + <span class="icon icon-videos-trending"></span> + <ng-container i18n>Trending</ng-container> + </a> - <a routerLink="/videos/trending" routerLinkActive="active"> - <span class="icon icon-videos-trending"></span> - <ng-container i18n>Trending</ng-container> - </a> + <a routerLink="/videos/recently-added" routerLinkActive="active"> + <span class="icon icon-videos-recently-added"></span> + <ng-container i18n>Recently added</ng-container> + </a> - <a routerLink="/videos/recently-added" routerLinkActive="active"> - <span class="icon icon-videos-recently-added"></span> - <ng-container i18n>Recently added</ng-container> - </a> + <a routerLink="/videos/local" routerLinkActive="active"> + <span class="icon icon-videos-local"></span> + <ng-container i18n>Local</ng-container> + </a> + </div> - <a routerLink="/videos/local" routerLinkActive="active"> - <span class="icon icon-videos-local"></span> - <ng-container i18n>Local</ng-container> - </a> - </div> + <div class="panel-block"> + <div class="block-title">More</div> - <div class="panel-block"> - <div class="block-title">More</div> + <a *ngIf="userHasAdminAccess" [routerLink]="getFirstAdminRouteAvailable()" routerLinkActive="active"> + <span class="icon icon-administration"></span> + <ng-container i18n>Administration</ng-container> + </a> - <a *ngIf="userHasAdminAccess" [routerLink]="getFirstAdminRouteAvailable()" routerLinkActive="active"> - <span class="icon icon-administration"></span> - <ng-container i18n>Administration</ng-container> - </a> + <a routerLink="/about" routerLinkActive="active"> + <span class="icon icon-about"></span> + <ng-container i18n>About</ng-container> + </a> + </div> + </div> + + <div class="footer"> + <span class="language"> + <span (click)="openLanguageChooser()" i18n-title title="Change the language" class="icon icon-language"></span> + </span> + </div> + </menu> +</div> - <a routerLink="/about" routerLinkActive="active"> - <span class="icon icon-about"></span> - <ng-container i18n>About</ng-container> - </a> - </div> -</menu> +<my-language-chooser #languageChooserModal></my-language-chooser> \ No newline at end of file diff --git a/client/src/app/menu/menu.component.scss b/client/src/app/menu/menu.component.scss index c36a7aa36c..e61f4acd33 100644 --- a/client/src/app/menu/menu.component.scss +++ b/client/src/app/menu/menu.component.scss @@ -1,6 +1,13 @@ @import '_variables'; @import '_mixins'; +.menu-wrapper { + position: fixed; + height: calc(100vh - #{$header-height}); + padding: 0; + width: $menu-width; +} + menu { background-color: $black-background; margin: 0; @@ -11,6 +18,13 @@ menu { overflow: hidden; z-index: 1000; color: $menu-color; + overflow-y: auto; + display: flex; + flex-direction: column; + + .top-menu { + flex-grow: 1; + } .logged-in-block { height: 100px; @@ -100,7 +114,7 @@ menu { a { display: flex; align-items: center; - padding-left: 26px; + padding-left: $menu-left-padding; color: $menu-color; cursor: pointer; height: 40px; @@ -155,4 +169,35 @@ menu { } } } + + .footer { + margin-bottom: 15px; + padding-left: $menu-left-padding; + + .language { + display: inline-block; + color: $menu-bottom-color; + cursor: pointer; + font-size: 12px; + font-weight: $font-semibold; + + .icon { + @include icon(28px); + opacity: 0.9; + + &.icon-language { + position: relative; + top: -1px; + width: 28px; + height: 24px; + + background-image: url('../../assets/images/menu/language.png'); + } + + &:hover { + opacity: 1; + } + } + } + } } diff --git a/client/src/app/menu/menu.component.ts b/client/src/app/menu/menu.component.ts index c0aea89b33..dded6b4d5a 100644 --- a/client/src/app/menu/menu.component.ts +++ b/client/src/app/menu/menu.component.ts @@ -1,8 +1,8 @@ -import { Component, OnInit } from '@angular/core' -import { Router } from '@angular/router' +import { Component, OnInit, ViewChild } from '@angular/core' import { UserRight } from '../../../../shared/models/users/user-right.enum' import { AuthService, AuthStatus, RedirectService, ServerService } from '../core' import { User } from '../shared/users/user.model' +import { LanguageChooserComponent } from '@app/menu/language-chooser.component' @Component({ selector: 'my-menu', @@ -10,6 +10,8 @@ import { User } from '../shared/users/user.model' styleUrls: [ './menu.component.scss' ] }) export class MenuComponent implements OnInit { + @ViewChild('languageChooserModal') languageChooserModal: LanguageChooserComponent + user: User isLoggedIn: boolean userHasAdminAccess = false @@ -90,6 +92,10 @@ export class MenuComponent implements OnInit { this.redirectService.redirectToHomepage() } + openLanguageChooser () { + this.languageChooserModal.show() + } + private computeIsUserHasAdminAccess () { const right = this.getFirstAdminRightAvailable() diff --git a/client/src/assets/images/menu/language.png b/client/src/assets/images/menu/language.png new file mode 100644 index 0000000000000000000000000000000000000000..60e6fec00fd20a3beae24b37729d8f16d1d7d476 GIT binary patch literal 10937 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 b+EL;^pfkr?BO-mc00000NkvXXu0mjfh7l6b literal 0 HcmV?d00001 diff --git a/client/src/sass/application.scss b/client/src/sass/application.scss index dae0c52c21..96602dc38d 100644 --- a/client/src/sass/application.scss +++ b/client/src/sass/application.scss @@ -288,7 +288,7 @@ table { // On small screen, menu is absolute @media screen and (max-width: 600px) { - .title-menu-left { + .menu-wrapper { width: 100% !important; position: absolute !important; z-index: 10000; diff --git a/client/src/sass/include/_variables.scss b/client/src/sass/include/_variables.scss index 092f8ed242..f1f7551264 100644 --- a/client/src/sass/include/_variables.scss +++ b/client/src/sass/include/_variables.scss @@ -22,7 +22,9 @@ $header-border-color: #e9eff6; $search-input-width: 375px; $menu-color: #fff; +$menu-bottom-color: #C6C6C6; $menu-width: 240px; +$menu-left-padding: 26px; $footer-height: 30px; $footer-margin: 30px; diff --git a/package.json b/package.json index edb14ff591..254281df50 100644 --- a/package.json +++ b/package.json @@ -84,6 +84,7 @@ "commander": "^2.13.0", "concurrently": "^3.5.1", "config": "^1.14.0", + "cookie-parser": "^1.4.3", "cors": "^2.8.1", "create-torrent": "^3.24.5", "express": "^4.12.4", diff --git a/server.ts b/server.ts index fb01ed572b..5511c54358 100644 --- a/server.ts +++ b/server.ts @@ -12,6 +12,7 @@ import * as bodyParser from 'body-parser' import * as express from 'express' import * as morgan from 'morgan' import * as cors from 'cors' +import * as cookieParser from 'cookie-parser' process.title = 'peertube' @@ -112,6 +113,8 @@ app.use(bodyParser.json({ type: [ 'application/json', 'application/*+json' ], limit: '500kb' })) +// Cookies +app.use(cookieParser()) // ----------- Views, routes and static files ----------- diff --git a/server/controllers/client.ts b/server/controllers/client.ts index 385757fa6b..dfffe5487d 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 import { asyncMiddleware } from '../middlewares' import { VideoModel } from '../models/video/video' import { VideoPrivacy } from '../../shared/models/videos' -import { buildFileLocale, getCompleteLocale, getDefaultLocale, is18nLocale } from '../../shared/models' -import { LOCALE_FILES } from '../../shared/models/i18n/i18n' +import { + buildFileLocale, + getCompleteLocale, + getDefaultLocale, + is18nLocale, + LOCALE_FILES, + POSSIBLE_LOCALES +} from '../../shared/models/i18n/i18n' const clientsRouter = express.Router() @@ -22,7 +28,8 @@ clientsRouter.use('/videos/watch/:id', asyncMiddleware(generateWatchHtmlPage) ) -clientsRouter.use('/videos/embed', (req: express.Request, res: express.Response, next: express.NextFunction) => { +clientsRouter.use('' + + '/videos/embed', (req: express.Request, res: express.Response, next: express.NextFunction) => { res.sendFile(embedPath) }) @@ -63,7 +70,7 @@ clientsRouter.use('/client/*', (req: express.Request, res: express.Response, nex // Try to provide the right language index.html clientsRouter.use('/(:language)?', function (req, res) { if (req.accepts(ACCEPT_HEADERS) === 'html') { - return res.sendFile(getIndexPath(req, req.params.language)) + return res.sendFile(getIndexPath(req, res, req.params.language)) } return res.status(404).end() @@ -77,16 +84,24 @@ export { // --------------------------------------------------------------------------- -function getIndexPath (req: express.Request, paramLang?: string) { +function getIndexPath (req: express.Request, res: express.Response, paramLang?: string) { let lang: string // Check param lang validity if (paramLang && is18nLocale(paramLang)) { lang = paramLang + + // Save locale in cookies + res.cookie('clientLanguage', lang, { + secure: CONFIG.WEBSERVER.SCHEME === 'https', + sameSite: true, + maxAge: 1000 * 3600 * 24 * 90 // 3 months + }) + + } else if (req.cookies.clientLanguage && is18nLocale(req.cookies.clientLanguage)) { + lang = req.cookies.clientLanguage } else { - // lang = req.acceptsLanguages(POSSIBLE_LOCALES) || getDefaultLocale() - // Disable auto language for now - lang = getDefaultLocale() + lang = req.acceptsLanguages(POSSIBLE_LOCALES) || getDefaultLocale() } return join(__dirname, '../../../client/dist/' + buildFileLocale(lang) + '/index.html') @@ -181,18 +196,18 @@ async function generateWatchHtmlPage (req: express.Request, res: express.Respons } else if (validator.isInt(videoId)) { videoPromise = VideoModel.loadAndPopulateAccountAndServerAndTags(+videoId) } else { - return res.sendFile(getIndexPath(req)) + return res.sendFile(getIndexPath(req, res)) } let [ file, video ] = await Promise.all([ - readFileBufferPromise(getIndexPath(req)), + readFileBufferPromise(getIndexPath(req, res)), videoPromise ]) const html = file.toString() // Let Angular application handle errors - if (!video || video.privacy === VideoPrivacy.PRIVATE) return res.sendFile(getIndexPath(req)) + if (!video || video.privacy === VideoPrivacy.PRIVATE) return res.sendFile(getIndexPath(req, res)) const htmlStringPageWithTags = addOpenGraphAndOEmbedTags(html, video) 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 e2b4409008..14b02a01d5 100644 --- a/shared/models/i18n/i18n.ts +++ b/shared/models/i18n/i18n.ts @@ -1,8 +1,8 @@ export const LOCALE_FILES = [ 'player', 'server' ] export const I18N_LOCALES = { - 'en-US': 'English (US)', - 'fr-FR': 'Français (France)' + 'en-US': 'English', + 'fr-FR': 'Français' } const I18N_LOCALE_ALIAS = { @@ -13,8 +13,6 @@ const I18N_LOCALE_ALIAS = { export const POSSIBLE_LOCALES = Object.keys(I18N_LOCALES) .concat(Object.keys(I18N_LOCALE_ALIAS)) -const possiblePaths = POSSIBLE_LOCALES.map(l => '/' + l) - export function getDefaultLocale () { return 'en-US' } @@ -23,6 +21,7 @@ export function isDefaultLocale (locale: string) { return getCompleteLocale(locale) === getCompleteLocale(getDefaultLocale()) } +const possiblePaths = POSSIBLE_LOCALES.map(l => '/' + l) export function is18nPath (path: string) { return possiblePaths.indexOf(path) !== -1 } diff --git a/yarn.lock b/yarn.lock index 65b78b4fa5..8c79ab282a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1590,6 +1590,13 @@ content-type@~1.0.1, content-type@~1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.4.tgz#e138cc75e040c727b1966fe5e5f8c9aee256fe3b" +cookie-parser@^1.4.3: + version "1.4.3" + resolved "https://registry.yarnpkg.com/cookie-parser/-/cookie-parser-1.4.3.tgz#0fe31fa19d000b95f4aadf1f53fdc2b8a203baa5" + dependencies: + cookie "0.3.1" + cookie-signature "1.0.6" + cookie-signature@1.0.6: version "1.0.6" resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c" -- GitLab