Prepare i18n files
This commit is contained in:
parent
1dd59831f8
commit
989e526abf
28 changed files with 853 additions and 166 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -25,3 +25,4 @@
|
|||
/logs/
|
||||
/server/tools/import-mediacore.ts
|
||||
/docker-volume/
|
||||
/.zanata-cache
|
||||
|
|
|
@ -19,7 +19,8 @@
|
|||
"ng": "ng",
|
||||
"postinstall": "npm rebuild node-sass && test -f angular-cli-patch.js && node angular-cli-patch.js || true",
|
||||
"webpack-bundle-analyzer": "webpack-bundle-analyzer",
|
||||
"webdriver-manager": "webdriver-manager"
|
||||
"webdriver-manager": "webdriver-manager",
|
||||
"ngx-extractor": "ngx-extractor"
|
||||
},
|
||||
"license": "GPLv3",
|
||||
"resolutions": {
|
||||
|
@ -47,6 +48,7 @@
|
|||
"@ngx-loading-bar/http-client": "^2.0.0",
|
||||
"@ngx-loading-bar/router": "^2.0.0",
|
||||
"@ngx-meta/core": "^6.0.0-rc.1",
|
||||
"@ngx-translate/i18n-polyfill": "^1.0.0",
|
||||
"@types/core-js": "^0.9.28",
|
||||
"@types/jasmine": "^2.8.7",
|
||||
"@types/jasminewd2": "^2.0.3",
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
import { Component, OnInit } from '@angular/core'
|
||||
import { DomSanitizer, SafeHtml } from '@angular/platform-browser'
|
||||
import { GuardsCheckStart, Router, NavigationEnd } from '@angular/router'
|
||||
import { GuardsCheckStart, NavigationEnd, Router } from '@angular/router'
|
||||
import { AuthService, RedirectService, ServerService } from '@app/core'
|
||||
import { isInSmallView } from '@app/shared/misc/utils'
|
||||
import { is18nPath } from '../../../shared/models/i18n'
|
||||
|
||||
@Component({
|
||||
selector: 'my-app',
|
||||
|
@ -33,7 +34,7 @@ export class AppComponent implements OnInit {
|
|||
private serverService: ServerService,
|
||||
private domSanitizer: DomSanitizer,
|
||||
private redirectService: RedirectService
|
||||
) {}
|
||||
) { }
|
||||
|
||||
get serverVersion () {
|
||||
return this.serverService.getConfig().serverVersion
|
||||
|
@ -53,7 +54,7 @@ export class AppComponent implements OnInit {
|
|||
this.router.events.subscribe(e => {
|
||||
if (e instanceof NavigationEnd) {
|
||||
const pathname = window.location.pathname
|
||||
if (!pathname || pathname === '/') {
|
||||
if (!pathname || pathname === '/' || is18nPath(pathname)) {
|
||||
this.redirectService.redirectToHomepage()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { NgModule } from '@angular/core'
|
||||
import { LOCALE_ID, NgModule, TRANSLATIONS, TRANSLATIONS_FORMAT } from '@angular/core'
|
||||
import { BrowserModule } from '@angular/platform-browser'
|
||||
import { AboutModule } from '@app/about'
|
||||
import { ServerService } from '@app/core'
|
||||
|
@ -16,6 +16,7 @@ import { MenuComponent } from './menu'
|
|||
import { SharedModule } from './shared'
|
||||
import { SignupModule } from './signup'
|
||||
import { VideosModule } from './videos'
|
||||
import { buildFileLocale, getDefaultLocale } from '../../../shared/models/i18n'
|
||||
|
||||
export function metaFactory (serverService: ServerService): MetaLoader {
|
||||
return new MetaStaticLoader({
|
||||
|
@ -61,6 +62,21 @@ export function metaFactory (serverService: ServerService): MetaLoader {
|
|||
|
||||
AppRoutingModule // Put it after all the module because it has the 404 route
|
||||
],
|
||||
providers: [ ]
|
||||
providers: [
|
||||
{
|
||||
provide: TRANSLATIONS,
|
||||
useFactory: (locale) => {
|
||||
const fileLocale = buildFileLocale(locale)
|
||||
|
||||
// Default locale, nothing to translate
|
||||
const defaultFileLocale = buildFileLocale(getDefaultLocale())
|
||||
if (fileLocale === defaultFileLocale) return ''
|
||||
|
||||
return require(`raw-loader!../locale/target/messages_${fileLocale}.xml`)
|
||||
},
|
||||
deps: [ LOCALE_ID ]
|
||||
},
|
||||
{ provide: TRANSLATIONS_FORMAT, useValue: 'xlf' }
|
||||
]
|
||||
})
|
||||
export class AppModule {}
|
||||
|
|
|
@ -31,7 +31,7 @@ export class RedirectService {
|
|||
redirectToHomepage () {
|
||||
console.log('Redirecting to %s...', RedirectService.DEFAULT_ROUTE)
|
||||
|
||||
this.router.navigate([ RedirectService.DEFAULT_ROUTE ], { replaceUrl: true })
|
||||
this.router.navigate([ RedirectService.DEFAULT_ROUTE ], { skipLocationChange: true })
|
||||
.catch(() => {
|
||||
console.error(
|
||||
'Cannot navigate to %s, resetting default route to %s.',
|
||||
|
@ -40,7 +40,7 @@ export class RedirectService {
|
|||
)
|
||||
|
||||
RedirectService.DEFAULT_ROUTE = RedirectService.INIT_DEFAULT_ROUTE
|
||||
return this.router.navigate([ RedirectService.DEFAULT_ROUTE ], { replaceUrl: true })
|
||||
return this.router.navigate([ RedirectService.DEFAULT_ROUTE ], { skipLocationChange: true })
|
||||
})
|
||||
|
||||
}
|
||||
|
|
|
@ -98,7 +98,7 @@ function lineFeedToHtml (obj: object, keyToNormalize: string) {
|
|||
|
||||
// Try to cache a little bit window.innerWidth
|
||||
let windowInnerWidth = window.innerWidth
|
||||
// setInterval(() => windowInnerWidth = window.innerWidth, 500)
|
||||
setInterval(() => windowInnerWidth = window.innerWidth, 500)
|
||||
|
||||
function isInSmallView () {
|
||||
return windowInnerWidth < 600
|
||||
|
|
|
@ -33,6 +33,7 @@ import { VideoThumbnailComponent } from './video/video-thumbnail.component'
|
|||
import { VideoService } from './video/video.service'
|
||||
import { AccountService } from '@app/shared/account/account.service'
|
||||
import { VideoChannelService } from '@app/shared/video-channel/video-channel.service'
|
||||
import { I18n } from '@ngx-translate/i18n-polyfill'
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
|
@ -108,7 +109,8 @@ import { VideoChannelService } from '@app/shared/video-channel/video-channel.ser
|
|||
VideoService,
|
||||
AccountService,
|
||||
MarkdownService,
|
||||
VideoChannelService
|
||||
VideoChannelService,
|
||||
I18n
|
||||
]
|
||||
})
|
||||
export class SharedModule { }
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
<div [hidden]="videoNotFound" id="video-element-wrapper">
|
||||
</div>
|
||||
|
||||
<div *ngIf="videoNotFound" id="video-not-found">Video not found :'(</div>
|
||||
<div i18n *ngIf="videoNotFound" id="video-not-found">Video not found :'(</div>
|
||||
|
||||
<!-- Video information -->
|
||||
<div *ngIf="video" class="margin-content video-bottom">
|
||||
|
@ -12,21 +12,21 @@
|
|||
<div>
|
||||
<div class="video-info-name">{{ video.name }}</div>
|
||||
|
||||
<div class="video-info-date-views">
|
||||
<div i18n class="video-info-date-views">
|
||||
{{ video.publishedAt | myFromNow }} - {{ video.views | myNumberFormatter }} views
|
||||
</div>
|
||||
|
||||
<div class="video-info-channel">
|
||||
<a [routerLink]="[ '/video-channels', video.channel.id ]" title="Go the channel page">
|
||||
<a [routerLink]="[ '/video-channels', video.channel.id ]" i18n-title title="Go the channel page">
|
||||
{{ video.channel.displayName }}
|
||||
</a>
|
||||
<!-- Here will be the subscribe button -->
|
||||
<my-help helpType="custom" customHtml="You can subscribe to this account via any ActivityPub-capable fediverse instance. For instance with Mastodon or Pleroma you can type in the search box <strong>@{{video.account.displayName}}@{{video.account.host}}</strong> and subscribe there. Subscription as a PeerTube user is being worked on in <a href='https://github.com/Chocobozzz/PeerTube/issues/470'>#470</a>."></my-help>
|
||||
<my-help helpType="custom" i18n-customHtml customHtml="You can subscribe to this account via any ActivityPub-capable fediverse instance. For instance with Mastodon or Pleroma you can type in the search box <strong>@{{video.account.displayName}}@{{video.account.host}}</strong> and subscribe there. Subscription as a PeerTube user is being worked on in <a href='https://github.com/Chocobozzz/PeerTube/issues/470'>#470</a>."></my-help>
|
||||
</div>
|
||||
|
||||
<div class="video-info-by">
|
||||
<a [routerLink]="[ '/accounts', video.by ]" title="Go the account page">
|
||||
<span>By {{ video.by }}</span>
|
||||
<a [routerLink]="[ '/accounts', video.by ]" i18n-title title="Go the account page">
|
||||
<span i18n>By {{ video.by }}</span>
|
||||
<img [src]="video.accountAvatarUrl" alt="Account avatar" />
|
||||
</a>
|
||||
</div>
|
||||
|
@ -38,24 +38,24 @@
|
|||
*ngIf="isUserLoggedIn()" [ngClass]="{ 'activated': userRating === 'like' }" (click)="setLike()"
|
||||
class="action-button action-button-like"
|
||||
>
|
||||
<span class="icon icon-like" title="Like this video" ></span>
|
||||
<span class="icon icon-like" i18n-title title="Like this video" ></span>
|
||||
</div>
|
||||
|
||||
<div
|
||||
*ngIf="isUserLoggedIn()" [ngClass]="{ 'activated': userRating === 'dislike' }" (click)="setDislike()"
|
||||
class="action-button action-button-dislike"
|
||||
>
|
||||
<span class="icon icon-dislike" title="Dislike this video"></span>
|
||||
<span class="icon icon-dislike" i18n-title title="Dislike this video"></span>
|
||||
</div>
|
||||
|
||||
<div *ngIf="video.support" (click)="showSupportModal()" class="action-button action-button-support">
|
||||
<span class="icon icon-support"></span>
|
||||
<span class="icon-text">Support</span>
|
||||
<span class="icon-text" i18n>Support</span>
|
||||
</div>
|
||||
|
||||
<div (click)="showShareModal()" class="action-button action-button-share">
|
||||
<span class="icon icon-share"></span>
|
||||
<span class="icon-text">Share</span>
|
||||
<span class="icon-text" i18n>Share</span>
|
||||
</div>
|
||||
|
||||
<div class="action-more" dropdown dropup="true" placement="right">
|
||||
|
@ -65,32 +65,32 @@
|
|||
|
||||
<ul *dropdownMenu class="dropdown-menu" id="more-menu" role="menu" aria-labelledby="single-button">
|
||||
<li role="menuitem">
|
||||
<a class="dropdown-item" title="Download the video" href="#" (click)="showDownloadModal($event)">
|
||||
<span class="icon icon-download"></span> Download
|
||||
<a class="dropdown-item" i18n-title title="Download the video" href="#" (click)="showDownloadModal($event)">
|
||||
<span class="icon icon-download"></span> <ng-container i18n>Download</ng-container>
|
||||
</a>
|
||||
</li>
|
||||
|
||||
<li *ngIf="isUserLoggedIn()" role="menuitem">
|
||||
<a class="dropdown-item" title="Report this video" href="#" (click)="showReportModal($event)">
|
||||
<span class="icon icon-alert"></span> Report
|
||||
<a class="dropdown-item" i18n-title title="Report this video" href="#" (click)="showReportModal($event)">
|
||||
<span class="icon icon-alert"></span> <ng-container i18n>Report</ng-container>
|
||||
</a>
|
||||
</li>
|
||||
|
||||
<li *ngIf="isVideoBlacklistable()" role="menuitem">
|
||||
<a class="dropdown-item" title="Blacklist this video" href="#" (click)="blacklistVideo($event)">
|
||||
<span class="icon icon-blacklist"></span> Blacklist
|
||||
<a class="dropdown-item" i18n-title title="Blacklist this video" href="#" (click)="blacklistVideo($event)">
|
||||
<span class="icon icon-blacklist"></span> <ng-container i18n>Blacklist</ng-container>
|
||||
</a>
|
||||
</li>
|
||||
|
||||
<li *ngIf="isVideoUpdatable()" role="menuitem">
|
||||
<a class="dropdown-item" title="Update this video" href="#" [routerLink]="[ '/videos/update', video.uuid ]">
|
||||
<span class="icon icon-edit"></span> Update
|
||||
<a class="dropdown-item" i18n-title title="Update this video" href="#" [routerLink]="[ '/videos/update', video.uuid ]">
|
||||
<span class="icon icon-edit"></span> <ng-container i18n>Update</ng-container>
|
||||
</a>
|
||||
</li>
|
||||
|
||||
<li *ngIf="isVideoRemovable()" role="menuitem">
|
||||
<a class="dropdown-item" title="Delete this video" href="#" (click)="removeVideo($event)">
|
||||
<span class="icon icon-blacklist"></span> Delete
|
||||
<a class="dropdown-item" i18n-title title="Delete this video" href="#" (click)="removeVideo($event)">
|
||||
<span class="icon icon-blacklist"></span> <ng-container i18n>Delete</ng-container>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
@ -109,20 +109,20 @@
|
|||
<div class="video-info-description-html" [innerHTML]="videoHTMLDescription"></div>
|
||||
|
||||
<div class="video-info-description-more" *ngIf="completeDescriptionShown === false && video.description?.length >= 250" (click)="showMoreDescription()">
|
||||
Show more
|
||||
<ng-container i18n>Show more</ng-container>
|
||||
<span *ngIf="descriptionLoading === false" class="glyphicon glyphicon-menu-down"></span>
|
||||
<my-loader class="description-loading" [loading]="descriptionLoading"></my-loader>
|
||||
</div>
|
||||
|
||||
<div *ngIf="completeDescriptionShown === true" (click)="showLessDescription()" class="video-info-description-more">
|
||||
Show less
|
||||
<ng-container i18n>Show less</ng-container>
|
||||
<span *ngIf="descriptionLoading === false" class="glyphicon glyphicon-menu-up"></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="video-attributes">
|
||||
<div class="video-attribute">
|
||||
<span class="video-attribute-label">
|
||||
<span i18n class="video-attribute-label">
|
||||
Privacy
|
||||
</span>
|
||||
<span class="video-attribute-value">
|
||||
|
@ -131,7 +131,7 @@
|
|||
</div>
|
||||
|
||||
<div class="video-attribute">
|
||||
<span class="video-attribute-label">
|
||||
<span i18n class="video-attribute-label">
|
||||
Category
|
||||
</span>
|
||||
<span class="video-attribute-value">
|
||||
|
@ -140,7 +140,7 @@
|
|||
</div>
|
||||
|
||||
<div class="video-attribute">
|
||||
<span class="video-attribute-label">
|
||||
<span i18n class="video-attribute-label">
|
||||
Licence
|
||||
</span>
|
||||
<span class="video-attribute-value">
|
||||
|
@ -149,7 +149,7 @@
|
|||
</div>
|
||||
|
||||
<div class="video-attribute">
|
||||
<span class="video-attribute-label">
|
||||
<span i18n class="video-attribute-label">
|
||||
Language
|
||||
</span>
|
||||
<span class="video-attribute-value">
|
||||
|
@ -158,7 +158,7 @@
|
|||
</div>
|
||||
|
||||
<div class="video-attribute">
|
||||
<span class="video-attribute-label">
|
||||
<span i18n class="video-attribute-label">
|
||||
Tags
|
||||
</span>
|
||||
|
||||
|
@ -172,7 +172,7 @@
|
|||
</div>
|
||||
|
||||
<div class="other-videos">
|
||||
<div class="title-page title-page-single">
|
||||
<div i18n class="title-page title-page-single">
|
||||
Other videos
|
||||
</div>
|
||||
|
||||
|
@ -184,13 +184,15 @@
|
|||
|
||||
|
||||
<div class="privacy-concerns" *ngIf="hasAlreadyAcceptedPrivacyConcern === false">
|
||||
<strong>Friendly Reminder:</strong>
|
||||
<strong i18n>Friendly Reminder:</strong>
|
||||
<div class="privacy-concerns-text">
|
||||
The sharing system used by this video implies that some technical information about your system (such as a public IP address) can be accessed publicly.
|
||||
<a title="Get more information" target="_blank" rel="noopener noreferrer" href="/about#p2p-privacy">More information</a>
|
||||
<ng-container i18n>
|
||||
The sharing system used by this video implies that some technical information about your system (such as a public IP address) can be accessed publicly.
|
||||
</ng-container>
|
||||
<a i18n i18n-title title="Get more information" target="_blank" rel="noopener noreferrer" href="/about#p2p-privacy">More information</a>
|
||||
</div>
|
||||
|
||||
<div class="privacy-concerns-okay" (click)="acceptedPrivacyConcern()">
|
||||
<div i18n class="privacy-concerns-okay" (click)="acceptedPrivacyConcern()">
|
||||
OK
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -23,6 +23,7 @@ import { VideoReportComponent } from './modal/video-report.component'
|
|||
import { VideoShareComponent } from './modal/video-share.component'
|
||||
import { getVideojsOptions } from '../../../assets/player/peertube-player'
|
||||
import { ServerService } from '@app/core'
|
||||
import { I18n } from '@ngx-translate/i18n-polyfill'
|
||||
|
||||
@Component({
|
||||
selector: 'my-video-watch',
|
||||
|
@ -70,7 +71,8 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
|
|||
private notificationsService: NotificationsService,
|
||||
private markdownService: MarkdownService,
|
||||
private zone: NgZone,
|
||||
private redirectService: RedirectService
|
||||
private redirectService: RedirectService,
|
||||
private i18n: I18n
|
||||
) {}
|
||||
|
||||
get user () {
|
||||
|
@ -153,17 +155,20 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
|
|||
async blacklistVideo (event: Event) {
|
||||
event.preventDefault()
|
||||
|
||||
const res = await this.confirmService.confirm('Do you really want to blacklist this video?', 'Blacklist')
|
||||
const res = await this.confirmService.confirm(this.i18n('Do you really want to blacklist this video?'), this.i18n('Blacklist'))
|
||||
if (res === false) return
|
||||
|
||||
this.videoBlacklistService.blacklistVideo(this.video.id)
|
||||
.subscribe(
|
||||
status => {
|
||||
this.notificationsService.success('Success', `Video ${this.video.name} had been blacklisted.`)
|
||||
this.notificationsService.success(
|
||||
this.i18n('Success'),
|
||||
this.i18n('Video {{ videoName }} had been blacklisted.', { videoName: this.video.name })
|
||||
)
|
||||
this.redirectService.redirectToHomepage()
|
||||
},
|
||||
|
||||
error => this.notificationsService.error('Error', error.message)
|
||||
error => this.notificationsService.error(this.i18n('Error'), error.message)
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -198,7 +203,7 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
|
|||
|
||||
error => {
|
||||
this.descriptionLoading = false
|
||||
this.notificationsService.error('Error', error.message)
|
||||
this.notificationsService.error(this.i18n('Error'), error.message)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
@ -252,19 +257,22 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
|
|||
async removeVideo (event: Event) {
|
||||
event.preventDefault()
|
||||
|
||||
const res = await this.confirmService.confirm('Do you really want to delete this video?', 'Delete')
|
||||
const res = await this.confirmService.confirm(this.i18n('Do you really want to delete this video?'), this.i18n('Delete'))
|
||||
if (res === false) return
|
||||
|
||||
this.videoService.removeVideo(this.video.id)
|
||||
.subscribe(
|
||||
status => {
|
||||
this.notificationsService.success('Success', `Video ${this.video.name} deleted.`)
|
||||
this.notificationsService.success(
|
||||
this.i18n('Success'),
|
||||
this.i18n('Video {{ videoName }} deleted.', { videoName: this.video.name })
|
||||
)
|
||||
|
||||
// Go back to the video-list.
|
||||
this.redirectService.redirectToHomepage()
|
||||
},
|
||||
|
||||
error => this.notificationsService.error('Error', error.message)
|
||||
error => this.notificationsService.error(this.i18n('Error'), error.message)
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -288,7 +296,10 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
|
|||
}
|
||||
|
||||
private setVideoLikesBarTooltipText () {
|
||||
this.likesBarTooltipText = `${this.video.likes} likes / ${this.video.dislikes} dislikes`
|
||||
this.likesBarTooltipText = this.i18n(
|
||||
'{{ likesNumber }} likes / {{ dislikesNumber }} dislikes',
|
||||
{ likesNumber: this.video.likes, dislikes: this.video.dislikes }
|
||||
)
|
||||
}
|
||||
|
||||
private handleError (err: any) {
|
||||
|
@ -298,12 +309,12 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
|
|||
let message = ''
|
||||
|
||||
if (errorMessage.indexOf('http error') !== -1) {
|
||||
message = 'Cannot fetch video from server, maybe down.'
|
||||
message = this.i18n('Cannot fetch video from server, maybe down.')
|
||||
} else {
|
||||
message = errorMessage
|
||||
}
|
||||
|
||||
this.notificationsService.error('Error', message)
|
||||
this.notificationsService.error(this.i18n('Error'), message)
|
||||
}
|
||||
|
||||
private checkUserRating () {
|
||||
|
@ -318,7 +329,7 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
|
|||
}
|
||||
},
|
||||
|
||||
err => this.notificationsService.error('Error', err.message)
|
||||
err => this.notificationsService.error(this.i18n('Error'), err.message)
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -333,8 +344,8 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
|
|||
|
||||
if (this.video.isVideoNSFWForUser(this.user, this.serverService.getConfig())) {
|
||||
const res = await this.confirmService.confirm(
|
||||
'This video contains mature or explicit content. Are you sure you want to watch it?',
|
||||
'Mature or explicit content'
|
||||
this.i18n('This video contains mature or explicit content. Are you sure you want to watch it?'),
|
||||
this.i18n('Mature or explicit content')
|
||||
)
|
||||
if (res === false) return this.redirectService.redirectToHomepage()
|
||||
}
|
||||
|
@ -399,7 +410,7 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
|
|||
this.updateVideoRating(this.userRating, nextRating)
|
||||
this.userRating = nextRating
|
||||
},
|
||||
err => this.notificationsService.error('Error', err.message)
|
||||
err => this.notificationsService.error(this.i18n('Error'), err.message)
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -8,6 +8,7 @@ import { AbstractVideoList } from '../../shared/video/abstract-video-list'
|
|||
import { VideoSortField } from '../../shared/video/sort-field.type'
|
||||
import { VideoService } from '../../shared/video/video.service'
|
||||
import { VideoFilter } from '../../../../../shared/models/videos/video-query.type'
|
||||
import { I18n } from '@ngx-translate/i18n-polyfill'
|
||||
|
||||
@Component({
|
||||
selector: 'my-videos-local',
|
||||
|
@ -15,18 +16,23 @@ import { VideoFilter } from '../../../../../shared/models/videos/video-query.typ
|
|||
templateUrl: '../../shared/video/abstract-video-list.html'
|
||||
})
|
||||
export class VideoLocalComponent extends AbstractVideoList implements OnInit, OnDestroy {
|
||||
titlePage = 'Local videos'
|
||||
titlePage: string
|
||||
currentRoute = '/videos/local'
|
||||
sort = '-publishedAt' as VideoSortField
|
||||
filter: VideoFilter = 'local'
|
||||
|
||||
constructor (protected router: Router,
|
||||
protected route: ActivatedRoute,
|
||||
protected notificationsService: NotificationsService,
|
||||
protected authService: AuthService,
|
||||
protected location: Location,
|
||||
private videoService: VideoService) {
|
||||
constructor (
|
||||
protected router: Router,
|
||||
protected route: ActivatedRoute,
|
||||
protected notificationsService: NotificationsService,
|
||||
protected authService: AuthService,
|
||||
protected location: Location,
|
||||
private videoService: VideoService,
|
||||
private i18n: I18n
|
||||
) {
|
||||
super()
|
||||
|
||||
this.titlePage = i18n('Local videos')
|
||||
}
|
||||
|
||||
ngOnInit () {
|
||||
|
|
|
@ -7,6 +7,7 @@ import { AuthService } from '../../core/auth'
|
|||
import { AbstractVideoList } from '../../shared/video/abstract-video-list'
|
||||
import { VideoSortField } from '../../shared/video/sort-field.type'
|
||||
import { VideoService } from '../../shared/video/video.service'
|
||||
import { I18n } from '@ngx-translate/i18n-polyfill'
|
||||
|
||||
@Component({
|
||||
selector: 'my-videos-recently-added',
|
||||
|
@ -14,17 +15,22 @@ import { VideoService } from '../../shared/video/video.service'
|
|||
templateUrl: '../../shared/video/abstract-video-list.html'
|
||||
})
|
||||
export class VideoRecentlyAddedComponent extends AbstractVideoList implements OnInit, OnDestroy {
|
||||
titlePage = 'Recently added'
|
||||
titlePage: string
|
||||
currentRoute = '/videos/recently-added'
|
||||
sort: VideoSortField = '-publishedAt'
|
||||
|
||||
constructor (protected router: Router,
|
||||
protected route: ActivatedRoute,
|
||||
protected location: Location,
|
||||
protected notificationsService: NotificationsService,
|
||||
protected authService: AuthService,
|
||||
private videoService: VideoService) {
|
||||
constructor (
|
||||
protected router: Router,
|
||||
protected route: ActivatedRoute,
|
||||
protected location: Location,
|
||||
protected notificationsService: NotificationsService,
|
||||
protected authService: AuthService,
|
||||
private videoService: VideoService,
|
||||
private i18n: I18n
|
||||
) {
|
||||
super()
|
||||
|
||||
this.titlePage = i18n('Recently added')
|
||||
}
|
||||
|
||||
ngOnInit () {
|
||||
|
|
|
@ -8,6 +8,7 @@ import { Subscription } from 'rxjs'
|
|||
import { AuthService } from '../../core/auth'
|
||||
import { AbstractVideoList } from '../../shared/video/abstract-video-list'
|
||||
import { VideoService } from '../../shared/video/video.service'
|
||||
import { I18n } from '@ngx-translate/i18n-polyfill'
|
||||
|
||||
@Component({
|
||||
selector: 'my-videos-search',
|
||||
|
@ -15,7 +16,7 @@ import { VideoService } from '../../shared/video/video.service'
|
|||
templateUrl: '../../shared/video/abstract-video-list.html'
|
||||
})
|
||||
export class VideoSearchComponent extends AbstractVideoList implements OnInit, OnDestroy {
|
||||
titlePage = 'Search'
|
||||
titlePage: string
|
||||
currentRoute = '/videos/search'
|
||||
loadOnInit = false
|
||||
|
||||
|
@ -24,15 +25,19 @@ export class VideoSearchComponent extends AbstractVideoList implements OnInit, O
|
|||
}
|
||||
private subActivatedRoute: Subscription
|
||||
|
||||
constructor (protected router: Router,
|
||||
protected route: ActivatedRoute,
|
||||
protected notificationsService: NotificationsService,
|
||||
protected authService: AuthService,
|
||||
protected location: Location,
|
||||
private videoService: VideoService,
|
||||
private redirectService: RedirectService
|
||||
constructor (
|
||||
protected router: Router,
|
||||
protected route: ActivatedRoute,
|
||||
protected notificationsService: NotificationsService,
|
||||
protected authService: AuthService,
|
||||
protected location: Location,
|
||||
private videoService: VideoService,
|
||||
private redirectService: RedirectService,
|
||||
private i18n: I18n
|
||||
) {
|
||||
super()
|
||||
|
||||
this.titlePage = i18n('Search')
|
||||
}
|
||||
|
||||
ngOnInit () {
|
||||
|
|
|
@ -7,6 +7,7 @@ import { AuthService } from '../../core/auth'
|
|||
import { AbstractVideoList } from '../../shared/video/abstract-video-list'
|
||||
import { VideoSortField } from '../../shared/video/sort-field.type'
|
||||
import { VideoService } from '../../shared/video/video.service'
|
||||
import { I18n } from '@ngx-translate/i18n-polyfill'
|
||||
|
||||
@Component({
|
||||
selector: 'my-videos-trending',
|
||||
|
@ -14,17 +15,22 @@ import { VideoService } from '../../shared/video/video.service'
|
|||
templateUrl: '../../shared/video/abstract-video-list.html'
|
||||
})
|
||||
export class VideoTrendingComponent extends AbstractVideoList implements OnInit, OnDestroy {
|
||||
titlePage = 'Trending'
|
||||
titlePage: string
|
||||
currentRoute = '/videos/trending'
|
||||
defaultSort: VideoSortField = '-views'
|
||||
|
||||
constructor (protected router: Router,
|
||||
protected route: ActivatedRoute,
|
||||
protected notificationsService: NotificationsService,
|
||||
protected authService: AuthService,
|
||||
protected location: Location,
|
||||
private videoService: VideoService) {
|
||||
constructor (
|
||||
protected router: Router,
|
||||
protected route: ActivatedRoute,
|
||||
protected notificationsService: NotificationsService,
|
||||
protected authService: AuthService,
|
||||
protected location: Location,
|
||||
private videoService: VideoService,
|
||||
private i18n: I18n
|
||||
) {
|
||||
super()
|
||||
|
||||
this.titlePage = i18n('Trending')
|
||||
}
|
||||
|
||||
ngOnInit () {
|
||||
|
|
354
client/src/locale/source/messages_en_US.xml
Normal file
354
client/src/locale/source/messages_en_US.xml
Normal file
|
@ -0,0 +1,354 @@
|
|||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<xliff version="1.2" xmlns="urn:oasis:names:tc:xliff:document:1.2">
|
||||
<file source-language="en-US" datatype="plaintext" original="ng2.template">
|
||||
<body>
|
||||
<trans-unit id="298cb43759c99e11e2ca5f92c768a145ddaa323f" datatype="html">
|
||||
<source>
|
||||
My public profile
|
||||
</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">app/menu/menu.component.ts</context>
|
||||
<context context-type="linenumber">17</context>
|
||||
</context-group>
|
||||
</trans-unit><trans-unit id="5f60990802486b7906b422d80aace6a1b19dcc02" datatype="html">
|
||||
<source>Video not found :'(</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">app/videos/+video-watch/video-watch.component.ts</context>
|
||||
<context context-type="linenumber">6</context>
|
||||
</context-group>
|
||||
</trans-unit><trans-unit id="643ab402461b1169eebbe2ed790e12a9a83551aa" datatype="html">
|
||||
<source>
|
||||
<x id="INTERPOLATION" equiv-text="{{ video.publishedAt | myFromNow }}"/> - <x id="INTERPOLATION_1" equiv-text="{{ video.views | myNumberFormatter }}"/> views
|
||||
</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">app/videos/+video-watch/video-watch.component.ts</context>
|
||||
<context context-type="linenumber">15</context>
|
||||
</context-group>
|
||||
</trans-unit><trans-unit id="5cb397241041f7ad70997806227bafcdf7eb1b33" datatype="html">
|
||||
<source>Go the channel page</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">app/videos/+video-watch/video-watch.component.ts</context>
|
||||
<context context-type="linenumber">20</context>
|
||||
</context-group>
|
||||
</trans-unit><trans-unit id="912f005563d20191efc188dccedd35a7c4e6b396" datatype="html">
|
||||
<source>You can subscribe to this account via any ActivityPub-capable fediverse instance. For instance with Mastodon or Pleroma you can type in the search box <strong>@<x id="INTERPOLATION" equiv-text="{{video.account.displayName}}"/>@<x id="INTERPOLATION_1" equiv-text="{{video.account.host}}"/></strong> and subscribe there. Subscription as a PeerTube user is being worked on in <a href='https://github.com/Chocobozzz/PeerTube/issues/470'>#470</a>.</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">app/videos/+video-watch/video-watch.component.ts</context>
|
||||
<context context-type="linenumber">24</context>
|
||||
</context-group>
|
||||
</trans-unit><trans-unit id="ccc07df383b7a32be3e2e105faa5488caf261c1c" datatype="html">
|
||||
<source>By <x id="INTERPOLATION" equiv-text="{{ video.by }}"/></source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">app/videos/+video-watch/video-watch.component.ts</context>
|
||||
<context context-type="linenumber">29</context>
|
||||
</context-group>
|
||||
</trans-unit><trans-unit id="e88300c71e0cb0f346d5a72eb37c920f2aadae8a" datatype="html">
|
||||
<source>Go the account page</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">app/videos/+video-watch/video-watch.component.ts</context>
|
||||
<context context-type="linenumber">28</context>
|
||||
</context-group>
|
||||
</trans-unit><trans-unit id="82b59049f3f89d900c98da9319e156dd513e3ced" datatype="html">
|
||||
<source>Like this video</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">app/videos/+video-watch/video-watch.component.ts</context>
|
||||
<context context-type="linenumber">41</context>
|
||||
</context-group>
|
||||
</trans-unit><trans-unit id="623698f075025b2b2fc2e0c59fd95f4f4662a509" datatype="html">
|
||||
<source>Dislike this video</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">app/videos/+video-watch/video-watch.component.ts</context>
|
||||
<context context-type="linenumber">48</context>
|
||||
</context-group>
|
||||
</trans-unit><trans-unit id="b5629d298ff1a69b8db19a4ba2995c76b52da604" datatype="html">
|
||||
<source>Support</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">app/videos/+video-watch/video-watch.component.ts</context>
|
||||
<context context-type="linenumber">53</context>
|
||||
</context-group>
|
||||
</trans-unit><trans-unit id="0bd8b27f60a1f098a53e06328426d818e3508ff9" datatype="html">
|
||||
<source>Share</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">app/videos/+video-watch/video-watch.component.ts</context>
|
||||
<context context-type="linenumber">58</context>
|
||||
</context-group>
|
||||
</trans-unit><trans-unit id="dc75033a5238fdc4f462212c847a45ba8018a3fd" datatype="html">
|
||||
<source>Download</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">app/videos/+video-watch/video-watch.component.ts</context>
|
||||
<context context-type="linenumber">69</context>
|
||||
</context-group>
|
||||
</trans-unit><trans-unit id="144fff5c40b85414d59e644d8dee7cfefba925a2" datatype="html">
|
||||
<source>Download the video</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">app/videos/+video-watch/video-watch.component.ts</context>
|
||||
<context context-type="linenumber">68</context>
|
||||
</context-group>
|
||||
</trans-unit><trans-unit id="f72992030f134408b675152c397f9d0ec00f3b2a" datatype="html">
|
||||
<source>Report</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">app/videos/+video-watch/video-watch.component.ts</context>
|
||||
<context context-type="linenumber">75</context>
|
||||
</context-group>
|
||||
</trans-unit><trans-unit id="2f4894617d9c44010f87473e583bd4604b7d6ecf" datatype="html">
|
||||
<source>Report this video</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">app/videos/+video-watch/video-watch.component.ts</context>
|
||||
<context context-type="linenumber">74</context>
|
||||
</context-group>
|
||||
</trans-unit><trans-unit id="007ab5fa2aae8a7372307d3fc45a2dbcb11ffd61" datatype="html">
|
||||
<source>Blacklist</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">app/videos/+video-watch/video-watch.component.ts</context>
|
||||
<context context-type="linenumber">81</context>
|
||||
</context-group>
|
||||
</trans-unit><trans-unit id="803c6317abd2dbafcc93226c4e273c62932e3037" datatype="html">
|
||||
<source>Blacklist this video</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">app/videos/+video-watch/video-watch.component.ts</context>
|
||||
<context context-type="linenumber">80</context>
|
||||
</context-group>
|
||||
</trans-unit><trans-unit id="047f50bc5b5d17b5bec0196355953e1a5c590ddb" datatype="html">
|
||||
<source>Update</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">app/videos/+video-watch/video-watch.component.ts</context>
|
||||
<context context-type="linenumber">87</context>
|
||||
</context-group>
|
||||
</trans-unit><trans-unit id="cd27f761b923a5bdb16ba9844da632edd878f1b1" datatype="html">
|
||||
<source>Update this video</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">app/videos/+video-watch/video-watch.component.ts</context>
|
||||
<context context-type="linenumber">86</context>
|
||||
</context-group>
|
||||
</trans-unit><trans-unit id="826b25211922a1b46436589233cb6f1a163d89b7" datatype="html">
|
||||
<source>Delete</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">app/videos/+video-watch/video-watch.component.ts</context>
|
||||
<context context-type="linenumber">93</context>
|
||||
</context-group>
|
||||
</trans-unit><trans-unit id="3dbfdc68f83d91cb360172eb65578cae94e7cbe5" datatype="html">
|
||||
<source>Delete this video</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">app/videos/+video-watch/video-watch.component.ts</context>
|
||||
<context context-type="linenumber">92</context>
|
||||
</context-group>
|
||||
</trans-unit><trans-unit id="f0c5f6f270e70cbe063b5368fcf48f9afc1abd9b" datatype="html">
|
||||
<source>Show more</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">app/videos/+video-watch/video-watch.component.ts</context>
|
||||
<context context-type="linenumber">112</context>
|
||||
</context-group>
|
||||
</trans-unit><trans-unit id="5403a767248e304199592271bba3366d2ca3f903" datatype="html">
|
||||
<source>Show less</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">app/videos/+video-watch/video-watch.component.ts</context>
|
||||
<context context-type="linenumber">118</context>
|
||||
</context-group>
|
||||
</trans-unit><trans-unit id="8057a9b7f9e908ff350edfd71417b96c174e5911" datatype="html">
|
||||
<source>
|
||||
Privacy
|
||||
</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">app/videos/+video-watch/video-watch.component.ts</context>
|
||||
<context context-type="linenumber">125</context>
|
||||
</context-group>
|
||||
</trans-unit><trans-unit id="bd407eca607a8905a26a9e30c9d0cd70f4465db8" datatype="html">
|
||||
<source>
|
||||
Category
|
||||
</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">app/videos/+video-watch/video-watch.component.ts</context>
|
||||
<context context-type="linenumber">134</context>
|
||||
</context-group>
|
||||
</trans-unit><trans-unit id="af5072bd79ea3cd767ab74a6622d2eee791b3832" datatype="html">
|
||||
<source>
|
||||
Licence
|
||||
</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">app/videos/+video-watch/video-watch.component.ts</context>
|
||||
<context context-type="linenumber">143</context>
|
||||
</context-group>
|
||||
</trans-unit><trans-unit id="a911eee019174741b0aec6fcf3fbd5752fab3e67" datatype="html">
|
||||
<source>
|
||||
Language
|
||||
</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">app/videos/+video-watch/video-watch.component.ts</context>
|
||||
<context context-type="linenumber">152</context>
|
||||
</context-group>
|
||||
</trans-unit><trans-unit id="ecf7007c2842cc26a7b91d08d48c7a4f5f749fb3" datatype="html">
|
||||
<source>
|
||||
Tags
|
||||
</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">app/videos/+video-watch/video-watch.component.ts</context>
|
||||
<context context-type="linenumber">161</context>
|
||||
</context-group>
|
||||
</trans-unit><trans-unit id="7ce8b0d7cc34d4c1ef4a21e990b0a001337bedd1" datatype="html">
|
||||
<source>
|
||||
Other videos
|
||||
</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">app/videos/+video-watch/video-watch.component.ts</context>
|
||||
<context context-type="linenumber">175</context>
|
||||
</context-group>
|
||||
</trans-unit><trans-unit id="fb779d2b25c4d0ffa7d52c823a240717e8c1fe6c" datatype="html">
|
||||
<source>Friendly Reminder:</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">app/videos/+video-watch/video-watch.component.ts</context>
|
||||
<context context-type="linenumber">187</context>
|
||||
</context-group>
|
||||
</trans-unit><trans-unit id="4c2fca29fd9d7e85abe85a206958a4226f403be2" datatype="html">
|
||||
<source>
|
||||
The sharing system used by this video implies that some technical information about your system (such as a public IP address) can be accessed publicly.
|
||||
</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">app/videos/+video-watch/video-watch.component.ts</context>
|
||||
<context context-type="linenumber">189</context>
|
||||
</context-group>
|
||||
</trans-unit><trans-unit id="e60c11e1b1dfbbeda577364b8de39ded2d796c5e" datatype="html">
|
||||
<source>More information</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">app/videos/+video-watch/video-watch.component.ts</context>
|
||||
<context context-type="linenumber">192</context>
|
||||
</context-group>
|
||||
</trans-unit><trans-unit id="bd499ca7913bb5408fd139a4cb4f863852d5f318" datatype="html">
|
||||
<source>Get more information</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">app/videos/+video-watch/video-watch.component.ts</context>
|
||||
<context context-type="linenumber">192</context>
|
||||
</context-group>
|
||||
</trans-unit><trans-unit id="20fc98888baf65b5ba9fe9622dc036fa8dec6a5f" datatype="html">
|
||||
<source>
|
||||
OK
|
||||
</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">app/videos/+video-watch/video-watch.component.ts</context>
|
||||
<context context-type="linenumber">195</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="23b2c2f4dd69e29c3bff00469e259dcb01de5633" datatype="html">
|
||||
<source>Do you really want to blacklist this video?</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/videos/+video-watch/video-watch.component.ts</context>
|
||||
<context context-type="linenumber">1</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="1e035e6ccfab771cad4226b2ad230cb0d4a88cba" datatype="html">
|
||||
<source>Success</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/videos/+video-watch/video-watch.component.ts</context>
|
||||
<context context-type="linenumber">1</context>
|
||||
</context-group>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/videos/+video-watch/video-watch.component.ts</context>
|
||||
<context context-type="linenumber">1</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="085d56464b75ae5c1e370f5290e4c4cf23961a61" datatype="html">
|
||||
<source>Video <x id="INTERPOLATION" equiv-text="{{ videoName }}"/> had been blacklisted.</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/videos/+video-watch/video-watch.component.ts</context>
|
||||
<context context-type="linenumber">1</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="6080b77234e92ad41bb52653b239c4c4f851317d" datatype="html">
|
||||
<source>Error</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/videos/+video-watch/video-watch.component.ts</context>
|
||||
<context context-type="linenumber">1</context>
|
||||
</context-group>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/videos/+video-watch/video-watch.component.ts</context>
|
||||
<context context-type="linenumber">1</context>
|
||||
</context-group>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/videos/+video-watch/video-watch.component.ts</context>
|
||||
<context context-type="linenumber">1</context>
|
||||
</context-group>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/videos/+video-watch/video-watch.component.ts</context>
|
||||
<context context-type="linenumber">1</context>
|
||||
</context-group>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/videos/+video-watch/video-watch.component.ts</context>
|
||||
<context context-type="linenumber">1</context>
|
||||
</context-group>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/videos/+video-watch/video-watch.component.ts</context>
|
||||
<context context-type="linenumber">1</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="f1abd89c9280323209e939fa9c30f6e5cda20c95" datatype="html">
|
||||
<source>Do you really want to delete this video?</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/videos/+video-watch/video-watch.component.ts</context>
|
||||
<context context-type="linenumber">1</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="007c1d7080cf6da1ac264b23705246f0c53e3114" datatype="html">
|
||||
<source>Video <x id="INTERPOLATION" equiv-text="{{ videoName }}"/> deleted.</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/videos/+video-watch/video-watch.component.ts</context>
|
||||
<context context-type="linenumber">1</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="cf9a064824f2fa3f01fd5544ad21032e33e60dca" datatype="html">
|
||||
<source><x id="INTERPOLATION" equiv-text="{{ likesNumber }}"/> likes / <x id="INTERPOLATION_1" equiv-text="{{ dislikesNumber }}"/> dislikes</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/videos/+video-watch/video-watch.component.ts</context>
|
||||
<context context-type="linenumber">1</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="4a400b174208188dcb46f2c23f4af9accfabaa3f" datatype="html">
|
||||
<source>Cannot fetch video from server, maybe down.</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/videos/+video-watch/video-watch.component.ts</context>
|
||||
<context context-type="linenumber">1</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="ed013c2c29216501c688e9cb5f3a1c9fd9147b71" datatype="html">
|
||||
<source>This video contains mature or explicit content. Are you sure you want to watch it?</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/videos/+video-watch/video-watch.component.ts</context>
|
||||
<context context-type="linenumber">1</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="5ba3d522e4146eefcbd5c222247c1e2423d27cd8" datatype="html">
|
||||
<source>Mature or explicit content</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/videos/+video-watch/video-watch.component.ts</context>
|
||||
<context context-type="linenumber">1</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="b6307f83d9f43bff8d5129a7888e89964ddc3f7f" datatype="html">
|
||||
<source>Local videos</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/videos/video-list/video-local.component.ts</context>
|
||||
<context context-type="linenumber">1</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="8d20c5f5dd30acbe71316544dab774393fd9c3c1" datatype="html">
|
||||
<source>Recently added</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/videos/video-list/video-recently-added.component.ts</context>
|
||||
<context context-type="linenumber">1</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="7e892ba15f2c6c17e83510e273b3e10fc32ea016" datatype="html">
|
||||
<source>Search</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/videos/video-list/video-search.component.ts</context>
|
||||
<context context-type="linenumber">1</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="b6b7986bc3721ac483baf20bc9a320529075c807" datatype="html">
|
||||
<source>Trending</source>
|
||||
<context-group purpose="location">
|
||||
<context context-type="sourcefile">src/app/videos/video-list/video-trending.component.ts</context>
|
||||
<context context-type="linenumber">1</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
</body>
|
||||
</file>
|
||||
</xliff>
|
191
client/src/locale/target/messages_fr.xml
Normal file
191
client/src/locale/target/messages_fr.xml
Normal file
|
@ -0,0 +1,191 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--XLIFF document generated by Zanata. Visit http://zanata.org for more infomation.-->
|
||||
<xliff xmlns="urn:oasis:names:tc:xliff:document:1.1" xmlns:xyz="urn:appInfo:Items" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="urn:oasis:names:tc:xliff:document:1.1 http://www.oasis-open.org/committees/xliff/documents/xliff-core-1.1.xsd" version="1.1">
|
||||
<file source-language="en-US" datatype="plaintext" original="" target-language="fr">
|
||||
<body>
|
||||
<trans-unit id="298cb43759c99e11e2ca5f92c768a145ddaa323f">
|
||||
<source>
|
||||
My public profile
|
||||
</source>
|
||||
<target>Mon profile public</target>
|
||||
<context-group name="null">
|
||||
<context context-type="linenumber">17</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="5f60990802486b7906b422d80aace6a1b19dcc02">
|
||||
<source>Video not found :'(</source>
|
||||
<target>Vidéo non trouvée :'(</target>
|
||||
<context-group name="null">
|
||||
<context context-type="linenumber">6</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="643ab402461b1169eebbe2ed790e12a9a83551aa">
|
||||
<source>
|
||||
<x id="INTERPOLATION" equiv-text="{{ video.publishedAt | myFromNow }}"/> - <x id="INTERPOLATION_1" equiv-text="{{ video.views | myNumberFormatter }}"/> views
|
||||
</source>
|
||||
<target>
|
||||
<x id="INTERPOLATION" equiv-text="{{ video.publishedAt | myFromNow }}"/> - <x id="INTERPOLATION_1" equiv-text="{{ video.views | myNumberFormatter }}"/> vues </target>
|
||||
<context-group name="null">
|
||||
<context context-type="linenumber">15</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="912f005563d20191efc188dccedd35a7c4e6b396">
|
||||
<source>You can subscribe to this account via any ActivityPub-capable fediverse instance. For instance with Mastodon or Pleroma you can type in the search box <strong>@<x id="INTERPOLATION" equiv-text="{{video.account.displayName}}"/>@<x id="INTERPOLATION_1" equiv-text="{{video.account.host}}"/></strong> and subscribe there. Subscription as a PeerTube user is being worked on in <a href='https://github.com/Chocobozzz/PeerTube/issues/470'>#470</a>.</source>
|
||||
<target>You can subscribe to this account via any ActivityPub-capable fediverse instance. For instance with Mastodon or Pleroma you can type in the search box <strong>@<x id="INTERPOLATION" equiv-text="{{video.account.displayName}}"/>@<x id="INTERPOLATION_1" equiv-text="{{video.account.host}}"/></strong> and subscribe there. Subscription as a PeerTube user is being worked on in <a href='https://github.com/Chocobozzz/PeerTube/issues/470'>#470</a>.</target>
|
||||
<context-group name="null">
|
||||
<context context-type="linenumber">24</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="ccc07df383b7a32be3e2e105faa5488caf261c1c">
|
||||
<source>By <x id="INTERPOLATION" equiv-text="{{ video.by }}"/></source>
|
||||
<target>Par <x id="INTERPOLATION" equiv-text="{{ video.by }}"/></target>
|
||||
<context-group name="null">
|
||||
<context context-type="linenumber">29</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="e88300c71e0cb0f346d5a72eb37c920f2aadae8a">
|
||||
<source>Go the account page</source>
|
||||
<target>Aller sur la page du compte</target>
|
||||
<context-group name="null">
|
||||
<context context-type="linenumber">28</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="82b59049f3f89d900c98da9319e156dd513e3ced">
|
||||
<source>Like this video</source>
|
||||
<target>J'aime cette vidéo</target>
|
||||
<context-group name="null">
|
||||
<context context-type="linenumber">41</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="623698f075025b2b2fc2e0c59fd95f4f4662a509">
|
||||
<source>Dislike this video</source>
|
||||
<target>Je n'aime pas cette vidéo</target>
|
||||
<context-group name="null">
|
||||
<context context-type="linenumber">48</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="b5629d298ff1a69b8db19a4ba2995c76b52da604">
|
||||
<source>Support</source>
|
||||
<target>Supporter</target>
|
||||
<context-group name="null">
|
||||
<context context-type="linenumber">53</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="0bd8b27f60a1f098a53e06328426d818e3508ff9">
|
||||
<source>Share</source>
|
||||
<target>Partager</target>
|
||||
<context-group name="null">
|
||||
<context context-type="linenumber">58</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="dc75033a5238fdc4f462212c847a45ba8018a3fd">
|
||||
<source>Download</source>
|
||||
<target>Télécharger</target>
|
||||
<context-group name="null">
|
||||
<context context-type="linenumber">69</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="144fff5c40b85414d59e644d8dee7cfefba925a2">
|
||||
<source>Download the video</source>
|
||||
<target>Télécharger la vidéo</target>
|
||||
<context-group name="null">
|
||||
<context context-type="linenumber">68</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="f72992030f134408b675152c397f9d0ec00f3b2a">
|
||||
<source>Report</source>
|
||||
<target>Signaler</target>
|
||||
<context-group name="null">
|
||||
<context context-type="linenumber">75</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="2f4894617d9c44010f87473e583bd4604b7d6ecf">
|
||||
<source>Report this video</source>
|
||||
<target>Signaler cette vidéo</target>
|
||||
<context-group name="null">
|
||||
<context context-type="linenumber">74</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="007ab5fa2aae8a7372307d3fc45a2dbcb11ffd61">
|
||||
<source>Blacklist</source>
|
||||
<target>Blacklister</target>
|
||||
<context-group name="null">
|
||||
<context context-type="linenumber">81</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="803c6317abd2dbafcc93226c4e273c62932e3037">
|
||||
<source>Blacklist this video</source>
|
||||
<target>Blacklister cette vidéo</target>
|
||||
<context-group name="null">
|
||||
<context context-type="linenumber">80</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="047f50bc5b5d17b5bec0196355953e1a5c590ddb">
|
||||
<source>Update</source>
|
||||
<target>Mettre à jour</target>
|
||||
<context-group name="null">
|
||||
<context context-type="linenumber">87</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="cd27f761b923a5bdb16ba9844da632edd878f1b1">
|
||||
<source>Update this video</source>
|
||||
<target>Mettre à jour cette vidéo</target>
|
||||
<context-group name="null">
|
||||
<context context-type="linenumber">86</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="826b25211922a1b46436589233cb6f1a163d89b7">
|
||||
<source>Delete</source>
|
||||
<target>Supprimer</target>
|
||||
<context-group name="null">
|
||||
<context context-type="linenumber">93</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="3dbfdc68f83d91cb360172eb65578cae94e7cbe5">
|
||||
<source>Delete this video</source>
|
||||
<target>Supprimer cette vidéo</target>
|
||||
<context-group name="null">
|
||||
<context context-type="linenumber">92</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="f0c5f6f270e70cbe063b5368fcf48f9afc1abd9b">
|
||||
<source>Show more</source>
|
||||
<target>Montrer plus</target>
|
||||
<context-group name="null">
|
||||
<context context-type="linenumber">112</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="5403a767248e304199592271bba3366d2ca3f903">
|
||||
<source>Show less</source>
|
||||
<target>Montrer moins</target>
|
||||
<context-group name="null">
|
||||
<context context-type="linenumber">118</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="8057a9b7f9e908ff350edfd71417b96c174e5911">
|
||||
<source>
|
||||
Privacy
|
||||
</source>
|
||||
<target>Visibilité</target>
|
||||
<context-group name="null">
|
||||
<context context-type="linenumber">125</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="bd407eca607a8905a26a9e30c9d0cd70f4465db8">
|
||||
<source>
|
||||
Category
|
||||
</source>
|
||||
<target>Catégorie</target>
|
||||
<context-group name="null">
|
||||
<context context-type="linenumber">134</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
<trans-unit id="b6b7986bc3721ac483baf20bc9a320529075c807">
|
||||
<source>Trending</source>
|
||||
<target>Tendances</target>
|
||||
<context-group name="null">
|
||||
<context context-type="linenumber">1</context>
|
||||
</context-group>
|
||||
</trans-unit>
|
||||
</body>
|
||||
</file></xliff>
|
|
@ -242,6 +242,14 @@
|
|||
dependencies:
|
||||
tslib "~1.9.0"
|
||||
|
||||
"@ngx-translate/i18n-polyfill@^1.0.0":
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@ngx-translate/i18n-polyfill/-/i18n-polyfill-1.0.0.tgz#145edb28bcfc1332e1bc25279eadf9d4ed0a20f8"
|
||||
dependencies:
|
||||
glob "7.1.2"
|
||||
tslib "^1.9.0"
|
||||
yargs "10.0.3"
|
||||
|
||||
"@nodelib/fs.stat@^1.0.1":
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-1.1.0.tgz#50c1e2260ac0ed9439a181de3725a0168d59c48a"
|
||||
|
@ -4189,6 +4197,17 @@ glob@7.0.x:
|
|||
once "^1.3.0"
|
||||
path-is-absolute "^1.0.0"
|
||||
|
||||
glob@7.1.2, glob@^7.0.0, glob@^7.0.3, glob@^7.0.5, glob@^7.0.6, glob@^7.1.1, glob@^7.1.2, glob@~7.1.1:
|
||||
version "7.1.2"
|
||||
resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.2.tgz#c19c9df9a028702d678612384a6552404c636d15"
|
||||
dependencies:
|
||||
fs.realpath "^1.0.0"
|
||||
inflight "^1.0.4"
|
||||
inherits "2"
|
||||
minimatch "^3.0.4"
|
||||
once "^1.3.0"
|
||||
path-is-absolute "^1.0.0"
|
||||
|
||||
glob@^5.0.15:
|
||||
version "5.0.15"
|
||||
resolved "https://registry.yarnpkg.com/glob/-/glob-5.0.15.tgz#1bc936b9e02f4a603fcc222ecf7633d30b8b93b1"
|
||||
|
@ -4209,17 +4228,6 @@ glob@^6.0.4:
|
|||
once "^1.3.0"
|
||||
path-is-absolute "^1.0.0"
|
||||
|
||||
glob@^7.0.0, glob@^7.0.3, glob@^7.0.5, glob@^7.0.6, glob@^7.1.1, glob@^7.1.2, glob@~7.1.1:
|
||||
version "7.1.2"
|
||||
resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.2.tgz#c19c9df9a028702d678612384a6552404c636d15"
|
||||
dependencies:
|
||||
fs.realpath "^1.0.0"
|
||||
inflight "^1.0.4"
|
||||
inherits "2"
|
||||
minimatch "^3.0.4"
|
||||
once "^1.3.0"
|
||||
path-is-absolute "^1.0.0"
|
||||
|
||||
global-modules@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/global-modules/-/global-modules-1.0.0.tgz#6d770f0eb523ac78164d72b5e71a8877265cc3ea"
|
||||
|
@ -10594,12 +10602,35 @@ yargs-parser@^7.0.0:
|
|||
dependencies:
|
||||
camelcase "^4.1.0"
|
||||
|
||||
yargs-parser@^8.0.0:
|
||||
version "8.1.0"
|
||||
resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-8.1.0.tgz#f1376a33b6629a5d063782944da732631e966950"
|
||||
dependencies:
|
||||
camelcase "^4.1.0"
|
||||
|
||||
yargs-parser@^9.0.2:
|
||||
version "9.0.2"
|
||||
resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-9.0.2.tgz#9ccf6a43460fe4ed40a9bb68f48d43b8a68cc077"
|
||||
dependencies:
|
||||
camelcase "^4.1.0"
|
||||
|
||||
yargs@10.0.3:
|
||||
version "10.0.3"
|
||||
resolved "https://registry.yarnpkg.com/yargs/-/yargs-10.0.3.tgz#6542debd9080ad517ec5048fb454efe9e4d4aaae"
|
||||
dependencies:
|
||||
cliui "^3.2.0"
|
||||
decamelize "^1.1.1"
|
||||
find-up "^2.1.0"
|
||||
get-caller-file "^1.0.1"
|
||||
os-locale "^2.0.0"
|
||||
require-directory "^2.1.1"
|
||||
require-main-filename "^1.0.1"
|
||||
set-blocking "^2.0.0"
|
||||
string-width "^2.0.0"
|
||||
which-module "^2.0.0"
|
||||
y18n "^3.2.1"
|
||||
yargs-parser "^8.0.0"
|
||||
|
||||
yargs@11.0.0:
|
||||
version "11.0.0"
|
||||
resolved "https://registry.yarnpkg.com/yargs/-/yargs-11.0.0.tgz#c052931006c5eee74610e5fc0354bedfd08a201b"
|
||||
|
|
|
@ -29,6 +29,7 @@
|
|||
"danger:clean:dev": "scripty",
|
||||
"danger:clean:prod": "scripty",
|
||||
"danger:clean:modules": "scripty",
|
||||
"i18n:generate": "scripty",
|
||||
"reset-password": "node ./dist/scripts/reset-password.js",
|
||||
"play": "scripty",
|
||||
"dev": "scripty",
|
||||
|
|
|
@ -6,5 +6,19 @@ cd client
|
|||
|
||||
rm -rf ./dist ./compiled
|
||||
|
||||
npm run ng build -- --prod --stats-json
|
||||
defaultLanguage="en-US"
|
||||
npm run ng build -- --output-path "dist/$defaultLanguage/" --deploy-url "/client/$defaultLanguage/" --prod --stats-json
|
||||
mv "./dist/$defaultLanguage/assets" "./dist"
|
||||
|
||||
languages="fr"
|
||||
|
||||
for lang in "$languages"; do
|
||||
npm run ng build -- --prod --i18n-file "./src/locale/target/messages_$lang.xml" --i18n-format xlf --i18n-locale "$lang" \
|
||||
--output-path "dist/$lang/" --deploy-url "/client/$lang/"
|
||||
|
||||
# Do no duplicate assets
|
||||
rm -r "./dist/$lang/assets"
|
||||
done
|
||||
|
||||
NODE_ENV=production npm run webpack -- --config webpack/webpack.video-embed.js --mode production
|
||||
|
||||
|
|
11
scripts/i18n/generate.sh
Executable file
11
scripts/i18n/generate.sh
Executable file
|
@ -0,0 +1,11 @@
|
|||
#!/bin/sh
|
||||
|
||||
set -eu
|
||||
|
||||
cd client
|
||||
npm run ng -- xi18n --i18n-locale "en-US" --output-path locale/source --out-file messages_en_US.xml
|
||||
npm run ngx-extractor -- --locale "en-US" -i 'src/**/*.ts' -f xlf -o src/locale/source/messages_en_US.xml
|
||||
|
||||
# Zanata does not support inner elements in <source>, so we hack these special elements
|
||||
# This regex translate the Angular elements to special entities (that we will reconvert on pull)
|
||||
sed -i 's/<x id=\([^\/]\+\?\)\/>/\<x id=\1\/\>/g' src/locale/source/messages_en_US.xml
|
7
scripts/i18n/pull-hook.sh
Executable file
7
scripts/i18n/pull-hook.sh
Executable file
|
@ -0,0 +1,7 @@
|
|||
#!/bin/sh
|
||||
|
||||
set -eu
|
||||
|
||||
# Zanata does not support inner elements in <source>, so we hack these special elements
|
||||
# This regex translate the converted elements to initial Angular elements
|
||||
sed -i 's/\<x id=\([^\/]\+\?\)\/\>/<x id=\1\/>/g' client/src/locale/target/*
|
|
@ -57,7 +57,7 @@ git commit package.json client/package.json -m "Bumped to version $version"
|
|||
git tag -s -a "$version" -m "$version"
|
||||
|
||||
npm run build
|
||||
rm "./client/dist/stats.json"
|
||||
rm "./client/dist/en-US/stats.json"
|
||||
|
||||
# Creating the archives
|
||||
(
|
||||
|
|
14
server.ts
14
server.ts
|
@ -12,7 +12,6 @@ import * as bodyParser from 'body-parser'
|
|||
import * as express from 'express'
|
||||
import * as http from 'http'
|
||||
import * as morgan from 'morgan'
|
||||
import * as path from 'path'
|
||||
import * as bitTorrentTracker from 'bittorrent-tracker'
|
||||
import * as cors from 'cors'
|
||||
import { Server as WebSocketServer } from 'ws'
|
||||
|
@ -156,20 +155,11 @@ app.use('/', activityPubRouter)
|
|||
app.use('/', feedsRouter)
|
||||
app.use('/', webfingerRouter)
|
||||
|
||||
// Client files
|
||||
app.use('/', clientsRouter)
|
||||
|
||||
// Static files
|
||||
app.use('/', staticRouter)
|
||||
|
||||
// Always serve index client page (the client is a single page application, let it handle routing)
|
||||
app.use('/*', function (req, res) {
|
||||
if (req.accepts(ACCEPT_HEADERS) === 'html') {
|
||||
return res.sendFile(path.join(__dirname, '../client/dist/index.html'))
|
||||
}
|
||||
|
||||
return res.status(404).end()
|
||||
})
|
||||
// Client files, last valid routes!
|
||||
app.use('/', clientsRouter)
|
||||
|
||||
// ----------- Errors -----------
|
||||
|
||||
|
|
|
@ -3,17 +3,24 @@ import * as express from 'express'
|
|||
import { join } from 'path'
|
||||
import * as validator from 'validator'
|
||||
import { escapeHTML, readFileBufferPromise, root } from '../helpers/core-utils'
|
||||
import { CONFIG, EMBED_SIZE, OPENGRAPH_AND_OEMBED_COMMENT, STATIC_MAX_AGE, STATIC_PATHS } from '../initializers'
|
||||
import {
|
||||
ACCEPT_HEADERS,
|
||||
CONFIG,
|
||||
EMBED_SIZE,
|
||||
OPENGRAPH_AND_OEMBED_COMMENT,
|
||||
STATIC_MAX_AGE,
|
||||
STATIC_PATHS
|
||||
} from '../initializers'
|
||||
import { asyncMiddleware } from '../middlewares'
|
||||
import { VideoModel } from '../models/video/video'
|
||||
import { VideoPrivacy } from '../../shared/models/videos'
|
||||
import { I18N_LOCALES, is18nLocale, getDefaultLocale } from '../../shared/models'
|
||||
|
||||
const clientsRouter = express.Router()
|
||||
|
||||
const distPath = join(root(), 'client', 'dist')
|
||||
const assetsImagesPath = join(root(), 'client', 'dist', 'assets', 'images')
|
||||
const embedPath = join(distPath, 'standalone', 'videos', 'embed.html')
|
||||
const indexPath = join(distPath, 'index.html')
|
||||
|
||||
// Special route that add OpenGraph and oEmbed tags
|
||||
// Do not use a template engine for a so little thing
|
||||
|
@ -45,6 +52,16 @@ clientsRouter.use('/client/*', (req: express.Request, res: express.Response, nex
|
|||
res.sendStatus(404)
|
||||
})
|
||||
|
||||
// Always serve index client page (the client is a single page application, let it handle routing)
|
||||
// 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.status(404).end()
|
||||
})
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export {
|
||||
|
@ -53,6 +70,19 @@ export {
|
|||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
function getIndexPath (req: express.Request, paramLang?: string) {
|
||||
let lang: string
|
||||
|
||||
// Check param lang validity
|
||||
if (paramLang && is18nLocale(paramLang)) {
|
||||
lang = paramLang
|
||||
} else {
|
||||
lang = req.acceptsLanguages(Object.keys(I18N_LOCALES)) || getDefaultLocale()
|
||||
}
|
||||
|
||||
return join(__dirname, '../../../client/dist/' + lang + '/index.html')
|
||||
}
|
||||
|
||||
function addOpenGraphAndOEmbedTags (htmlStringPage: string, video: VideoModel) {
|
||||
const previewUrl = CONFIG.WEBSERVER.URL + STATIC_PATHS.PREVIEWS + video.getPreviewName()
|
||||
const videoUrl = CONFIG.WEBSERVER.URL + '/videos/watch/' + video.uuid
|
||||
|
@ -142,18 +172,18 @@ async function generateWatchHtmlPage (req: express.Request, res: express.Respons
|
|||
} else if (validator.isInt(videoId)) {
|
||||
videoPromise = VideoModel.loadAndPopulateAccountAndServerAndTags(+videoId)
|
||||
} else {
|
||||
return res.sendFile(indexPath)
|
||||
return res.sendFile(getIndexPath(req))
|
||||
}
|
||||
|
||||
let [ file, video ] = await Promise.all([
|
||||
readFileBufferPromise(indexPath),
|
||||
readFileBufferPromise(getIndexPath(req)),
|
||||
videoPromise
|
||||
])
|
||||
|
||||
const html = file.toString()
|
||||
|
||||
// Let Angular application handle errors
|
||||
if (!video || video.privacy === VideoPrivacy.PRIVATE) return res.sendFile(indexPath)
|
||||
if (!video || video.privacy === VideoPrivacy.PRIVATE) return res.sendFile(getIndexPath(req))
|
||||
|
||||
const htmlStringPageWithTags = addOpenGraphAndOEmbedTags(html, video)
|
||||
res.set('Content-Type', 'text/html; charset=UTF-8').send(htmlStringPageWithTags)
|
||||
|
|
30
shared/models/i18n/i18n.ts
Normal file
30
shared/models/i18n/i18n.ts
Normal file
|
@ -0,0 +1,30 @@
|
|||
export const I18N_LOCALES = {
|
||||
'en-US': 'English (US)',
|
||||
fr: 'French'
|
||||
}
|
||||
|
||||
export function getDefaultLocale () {
|
||||
return 'en-US'
|
||||
}
|
||||
|
||||
const possiblePaths = Object.keys(I18N_LOCALES).map(l => '/' + l)
|
||||
export function is18nPath (path: string) {
|
||||
return possiblePaths.indexOf(path) !== -1
|
||||
}
|
||||
|
||||
const possibleLanguages = Object.keys(I18N_LOCALES)
|
||||
export function is18nLocale (locale: string) {
|
||||
return possibleLanguages.indexOf(locale) !== -1
|
||||
}
|
||||
|
||||
// Only use in dev mode, so relax
|
||||
// In production, the locale always match with a I18N_LANGUAGES key
|
||||
export function buildFileLocale (locale: string) {
|
||||
if (!is18nLocale(locale)) {
|
||||
// Some working examples for development purpose
|
||||
if (locale.split('-')[ 0 ] === 'en') return 'en_US'
|
||||
else if (locale === 'fr') return 'fr'
|
||||
}
|
||||
|
||||
return locale.replace('-', '_')
|
||||
}
|
1
shared/models/i18n/index.ts
Normal file
1
shared/models/i18n/index.ts
Normal file
|
@ -0,0 +1 @@
|
|||
export * from './i18n'
|
|
@ -3,6 +3,7 @@ export * from './activitypub'
|
|||
export * from './users'
|
||||
export * from './videos'
|
||||
export * from './feeds'
|
||||
export * from './i18n'
|
||||
export * from './server/job.model'
|
||||
export * from './oauth-client-local.model'
|
||||
export * from './result-list.model'
|
||||
|
|
53
yarn.lock
53
yarn.lock
|
@ -1237,7 +1237,7 @@ chalk@^1.0.0, chalk@^1.1.1, chalk@^1.1.3:
|
|||
strip-ansi "^3.0.0"
|
||||
supports-color "^2.0.0"
|
||||
|
||||
chalk@^2.0.0, chalk@^2.0.1, chalk@^2.1.0, chalk@^2.3.0, chalk@^2.3.1, chalk@^2.3.2:
|
||||
chalk@^2.0.0, chalk@^2.0.1, chalk@^2.1.0, chalk@^2.3.0, chalk@^2.3.1:
|
||||
version "2.4.1"
|
||||
resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.1.tgz#18c49ab16a037b6eb0152cc83e3471338215b66e"
|
||||
dependencies:
|
||||
|
@ -1517,7 +1517,7 @@ command-exists@^1.2.2:
|
|||
version "1.2.6"
|
||||
resolved "https://registry.yarnpkg.com/command-exists/-/command-exists-1.2.6.tgz#577f8e5feb0cb0f159cd557a51a9be1bdd76e09e"
|
||||
|
||||
commander@*, commander@2.15.1, commander@^2.12.1, commander@^2.13.0, commander@^2.14.1, commander@^2.15.1, commander@^2.8.1, commander@^2.9.0:
|
||||
commander@*, commander@2.15.1, commander@^2.12.1, commander@^2.13.0, commander@^2.14.1, commander@^2.8.1, commander@^2.9.0:
|
||||
version "2.15.1"
|
||||
resolved "https://registry.yarnpkg.com/commander/-/commander-2.15.1.tgz#df46e867d0fc2aec66a34662b406a9ccafff5b0f"
|
||||
|
||||
|
@ -2175,12 +2175,6 @@ error-ex@^1.2.0, error-ex@^1.3.1:
|
|||
dependencies:
|
||||
is-arrayish "^0.2.1"
|
||||
|
||||
error-stack-parser@^2.0.1:
|
||||
version "2.0.1"
|
||||
resolved "https://registry.yarnpkg.com/error-stack-parser/-/error-stack-parser-2.0.1.tgz#a3202b8fb03114aa9b40a0e3669e48b2b65a010a"
|
||||
dependencies:
|
||||
stackframe "^1.0.3"
|
||||
|
||||
es5-ext@^0.10.14, es5-ext@^0.10.35, es5-ext@^0.10.9, es5-ext@~0.10.14:
|
||||
version "0.10.43"
|
||||
resolved "https://registry.yarnpkg.com/es5-ext/-/es5-ext-0.10.43.tgz#c705e645253210233a270869aa463a2333b7ca64"
|
||||
|
@ -4155,7 +4149,7 @@ js-tokens@^3.0.0, js-tokens@^3.0.2:
|
|||
version "3.0.2"
|
||||
resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-3.0.2.tgz#9866df395102130e38f7f996bceb65443209c25b"
|
||||
|
||||
js-yaml@^3.11.0, js-yaml@^3.4.6, js-yaml@^3.5.1, js-yaml@^3.5.4, js-yaml@^3.7.0, js-yaml@^3.8.2, js-yaml@^3.8.3, js-yaml@^3.9.0:
|
||||
js-yaml@^3.4.6, js-yaml@^3.5.1, js-yaml@^3.5.4, js-yaml@^3.7.0, js-yaml@^3.8.2, js-yaml@^3.8.3, js-yaml@^3.9.0:
|
||||
version "3.11.0"
|
||||
resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.11.0.tgz#597c1a8bd57152f26d622ce4117851a51f5ebaef"
|
||||
dependencies:
|
||||
|
@ -6741,18 +6735,6 @@ sass-graph@^2.2.4:
|
|||
scss-tokenizer "^0.2.3"
|
||||
yargs "^7.0.0"
|
||||
|
||||
sass-lint-auto-fix@^0.9.0:
|
||||
version "0.9.2"
|
||||
resolved "https://registry.yarnpkg.com/sass-lint-auto-fix/-/sass-lint-auto-fix-0.9.2.tgz#b8b6eb95644f7919dfea33d04c1fc19ae8f07a11"
|
||||
dependencies:
|
||||
chalk "^2.3.2"
|
||||
commander "^2.15.1"
|
||||
glob "^7.1.2"
|
||||
gonzales-pe-sl "^4.2.3"
|
||||
js-yaml "^3.11.0"
|
||||
sass-lint "^1.12.1"
|
||||
stacktrace-js "^2.0.0"
|
||||
|
||||
sass-lint@^1.12.1:
|
||||
version "1.12.1"
|
||||
resolved "https://registry.yarnpkg.com/sass-lint/-/sass-lint-1.12.1.tgz#630f69c216aa206b8232fb2aa907bdf3336b6d83"
|
||||
|
@ -7194,10 +7176,6 @@ source-map@0.4.x, source-map@^0.4.2, source-map@^0.4.4:
|
|||
dependencies:
|
||||
amdefine ">=0.0.4"
|
||||
|
||||
source-map@0.5.6:
|
||||
version "0.5.6"
|
||||
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.6.tgz#75ce38f52bf0733c5a7f0c118d81334a2bb5f412"
|
||||
|
||||
source-map@0.5.x, source-map@^0.5.6, source-map@~0.5.1:
|
||||
version "0.5.7"
|
||||
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc"
|
||||
|
@ -7315,12 +7293,6 @@ stack-chain@1.3.x, stack-chain@~1.3.1:
|
|||
version "1.3.7"
|
||||
resolved "https://registry.yarnpkg.com/stack-chain/-/stack-chain-1.3.7.tgz#d192c9ff4ea6a22c94c4dd459171e3f00cea1285"
|
||||
|
||||
stack-generator@^2.0.1:
|
||||
version "2.0.2"
|
||||
resolved "https://registry.yarnpkg.com/stack-generator/-/stack-generator-2.0.2.tgz#3c13d952a596ab9318fec0669d0a1df8b87176c7"
|
||||
dependencies:
|
||||
stackframe "^1.0.4"
|
||||
|
||||
stack-trace@0.0.x:
|
||||
version "0.0.10"
|
||||
resolved "https://registry.yarnpkg.com/stack-trace/-/stack-trace-0.0.10.tgz#547c70b347e8d32b4e108ea1a2a159e5fdde19c0"
|
||||
|
@ -7329,25 +7301,6 @@ stack-utils@^1.0.1:
|
|||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/stack-utils/-/stack-utils-1.0.1.tgz#d4f33ab54e8e38778b0ca5cfd3b3afb12db68620"
|
||||
|
||||
stackframe@^1.0.3, stackframe@^1.0.4:
|
||||
version "1.0.4"
|
||||
resolved "https://registry.yarnpkg.com/stackframe/-/stackframe-1.0.4.tgz#357b24a992f9427cba6b545d96a14ed2cbca187b"
|
||||
|
||||
stacktrace-gps@^3.0.1:
|
||||
version "3.0.2"
|
||||
resolved "https://registry.yarnpkg.com/stacktrace-gps/-/stacktrace-gps-3.0.2.tgz#33f8baa4467323ab2bd1816efa279942ba431ccc"
|
||||
dependencies:
|
||||
source-map "0.5.6"
|
||||
stackframe "^1.0.4"
|
||||
|
||||
stacktrace-js@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/stacktrace-js/-/stacktrace-js-2.0.0.tgz#776ca646a95bc6c6b2b90776536a7fc72c6ddb58"
|
||||
dependencies:
|
||||
error-stack-parser "^2.0.1"
|
||||
stack-generator "^2.0.1"
|
||||
stacktrace-gps "^3.0.1"
|
||||
|
||||
staged-git-files@1.1.1:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/staged-git-files/-/staged-git-files-1.1.1.tgz#37c2218ef0d6d26178b1310719309a16a59f8f7b"
|
||||
|
|
15
zanata.xml
Normal file
15
zanata.xml
Normal file
|
@ -0,0 +1,15 @@
|
|||
<?xml version='1.0' encoding='UTF-8' standalone='yes'?>
|
||||
<config xmlns="http://zanata.org/namespace/config/">
|
||||
<url>https://trad.framasoft.org/zanata/</url>
|
||||
<project>peertube</project>
|
||||
<project-version>develop</project-version>
|
||||
<project-type>xliff</project-type>
|
||||
<src-dir>./client/src/locale/source</src-dir>
|
||||
<trans-dir>./client/src/locale/target</trans-dir>
|
||||
|
||||
<hooks>
|
||||
<hook command="pull">
|
||||
<after>./scripts/i18n/pull-hook.sh</after>
|
||||
</hook>
|
||||
</hooks>
|
||||
</config>
|
Loading…
Reference in a new issue