1
0
Fork 0

Prepare i18n files

This commit is contained in:
Chocobozzz 2018-05-31 18:12:15 +02:00
parent 1dd59831f8
commit 989e526abf
No known key found for this signature in database
GPG key ID: 583A612D890159BE
28 changed files with 853 additions and 166 deletions

1
.gitignore vendored
View file

@ -25,3 +25,4 @@
/logs/ /logs/
/server/tools/import-mediacore.ts /server/tools/import-mediacore.ts
/docker-volume/ /docker-volume/
/.zanata-cache

View file

@ -19,7 +19,8 @@
"ng": "ng", "ng": "ng",
"postinstall": "npm rebuild node-sass && test -f angular-cli-patch.js && node angular-cli-patch.js || true", "postinstall": "npm rebuild node-sass && test -f angular-cli-patch.js && node angular-cli-patch.js || true",
"webpack-bundle-analyzer": "webpack-bundle-analyzer", "webpack-bundle-analyzer": "webpack-bundle-analyzer",
"webdriver-manager": "webdriver-manager" "webdriver-manager": "webdriver-manager",
"ngx-extractor": "ngx-extractor"
}, },
"license": "GPLv3", "license": "GPLv3",
"resolutions": { "resolutions": {
@ -47,6 +48,7 @@
"@ngx-loading-bar/http-client": "^2.0.0", "@ngx-loading-bar/http-client": "^2.0.0",
"@ngx-loading-bar/router": "^2.0.0", "@ngx-loading-bar/router": "^2.0.0",
"@ngx-meta/core": "^6.0.0-rc.1", "@ngx-meta/core": "^6.0.0-rc.1",
"@ngx-translate/i18n-polyfill": "^1.0.0",
"@types/core-js": "^0.9.28", "@types/core-js": "^0.9.28",
"@types/jasmine": "^2.8.7", "@types/jasmine": "^2.8.7",
"@types/jasminewd2": "^2.0.3", "@types/jasminewd2": "^2.0.3",

View file

@ -1,8 +1,9 @@
import { Component, OnInit } from '@angular/core' import { Component, OnInit } from '@angular/core'
import { DomSanitizer, SafeHtml } from '@angular/platform-browser' 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 { AuthService, RedirectService, ServerService } from '@app/core'
import { isInSmallView } from '@app/shared/misc/utils' import { isInSmallView } from '@app/shared/misc/utils'
import { is18nPath } from '../../../shared/models/i18n'
@Component({ @Component({
selector: 'my-app', selector: 'my-app',
@ -33,7 +34,7 @@ export class AppComponent implements OnInit {
private serverService: ServerService, private serverService: ServerService,
private domSanitizer: DomSanitizer, private domSanitizer: DomSanitizer,
private redirectService: RedirectService private redirectService: RedirectService
) {} ) { }
get serverVersion () { get serverVersion () {
return this.serverService.getConfig().serverVersion return this.serverService.getConfig().serverVersion
@ -53,7 +54,7 @@ export class AppComponent implements OnInit {
this.router.events.subscribe(e => { this.router.events.subscribe(e => {
if (e instanceof NavigationEnd) { if (e instanceof NavigationEnd) {
const pathname = window.location.pathname const pathname = window.location.pathname
if (!pathname || pathname === '/') { if (!pathname || pathname === '/' || is18nPath(pathname)) {
this.redirectService.redirectToHomepage() this.redirectService.redirectToHomepage()
} }
} }

View file

@ -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 { BrowserModule } from '@angular/platform-browser'
import { AboutModule } from '@app/about' import { AboutModule } from '@app/about'
import { ServerService } from '@app/core' import { ServerService } from '@app/core'
@ -16,6 +16,7 @@ import { MenuComponent } from './menu'
import { SharedModule } from './shared' import { SharedModule } from './shared'
import { SignupModule } from './signup' import { SignupModule } from './signup'
import { VideosModule } from './videos' import { VideosModule } from './videos'
import { buildFileLocale, getDefaultLocale } from '../../../shared/models/i18n'
export function metaFactory (serverService: ServerService): MetaLoader { export function metaFactory (serverService: ServerService): MetaLoader {
return new MetaStaticLoader({ 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 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 {} export class AppModule {}

View file

@ -31,7 +31,7 @@ export class RedirectService {
redirectToHomepage () { redirectToHomepage () {
console.log('Redirecting to %s...', RedirectService.DEFAULT_ROUTE) 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(() => { .catch(() => {
console.error( console.error(
'Cannot navigate to %s, resetting default route to %s.', 'Cannot navigate to %s, resetting default route to %s.',
@ -40,7 +40,7 @@ export class RedirectService {
) )
RedirectService.DEFAULT_ROUTE = RedirectService.INIT_DEFAULT_ROUTE 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 })
}) })
} }

View file

@ -98,7 +98,7 @@ function lineFeedToHtml (obj: object, keyToNormalize: string) {
// Try to cache a little bit window.innerWidth // Try to cache a little bit window.innerWidth
let windowInnerWidth = window.innerWidth let windowInnerWidth = window.innerWidth
// setInterval(() => windowInnerWidth = window.innerWidth, 500) setInterval(() => windowInnerWidth = window.innerWidth, 500)
function isInSmallView () { function isInSmallView () {
return windowInnerWidth < 600 return windowInnerWidth < 600

View file

@ -33,6 +33,7 @@ import { VideoThumbnailComponent } from './video/video-thumbnail.component'
import { VideoService } from './video/video.service' import { VideoService } from './video/video.service'
import { AccountService } from '@app/shared/account/account.service' import { AccountService } from '@app/shared/account/account.service'
import { VideoChannelService } from '@app/shared/video-channel/video-channel.service' import { VideoChannelService } from '@app/shared/video-channel/video-channel.service'
import { I18n } from '@ngx-translate/i18n-polyfill'
@NgModule({ @NgModule({
imports: [ imports: [
@ -108,7 +109,8 @@ import { VideoChannelService } from '@app/shared/video-channel/video-channel.ser
VideoService, VideoService,
AccountService, AccountService,
MarkdownService, MarkdownService,
VideoChannelService VideoChannelService,
I18n
] ]
}) })
export class SharedModule { } export class SharedModule { }

View file

@ -3,7 +3,7 @@
<div [hidden]="videoNotFound" id="video-element-wrapper"> <div [hidden]="videoNotFound" id="video-element-wrapper">
</div> </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 --> <!-- Video information -->
<div *ngIf="video" class="margin-content video-bottom"> <div *ngIf="video" class="margin-content video-bottom">
@ -12,21 +12,21 @@
<div> <div>
<div class="video-info-name">{{ video.name }}</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 {{ video.publishedAt | myFromNow }} - {{ video.views | myNumberFormatter }} views
</div> </div>
<div class="video-info-channel"> <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 }} {{ video.channel.displayName }}
</a> </a>
<!-- Here will be the subscribe button --> <!-- 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>
<div class="video-info-by"> <div class="video-info-by">
<a [routerLink]="[ '/accounts', video.by ]" title="Go the account page"> <a [routerLink]="[ '/accounts', video.by ]" i18n-title title="Go the account page">
<span>By {{ video.by }}</span> <span i18n>By {{ video.by }}</span>
<img [src]="video.accountAvatarUrl" alt="Account avatar" /> <img [src]="video.accountAvatarUrl" alt="Account avatar" />
</a> </a>
</div> </div>
@ -38,24 +38,24 @@
*ngIf="isUserLoggedIn()" [ngClass]="{ 'activated': userRating === 'like' }" (click)="setLike()" *ngIf="isUserLoggedIn()" [ngClass]="{ 'activated': userRating === 'like' }" (click)="setLike()"
class="action-button action-button-like" 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>
<div <div
*ngIf="isUserLoggedIn()" [ngClass]="{ 'activated': userRating === 'dislike' }" (click)="setDislike()" *ngIf="isUserLoggedIn()" [ngClass]="{ 'activated': userRating === 'dislike' }" (click)="setDislike()"
class="action-button action-button-dislike" 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>
<div *ngIf="video.support" (click)="showSupportModal()" class="action-button action-button-support"> <div *ngIf="video.support" (click)="showSupportModal()" class="action-button action-button-support">
<span class="icon icon-support"></span> <span class="icon icon-support"></span>
<span class="icon-text">Support</span> <span class="icon-text" i18n>Support</span>
</div> </div>
<div (click)="showShareModal()" class="action-button action-button-share"> <div (click)="showShareModal()" class="action-button action-button-share">
<span class="icon icon-share"></span> <span class="icon icon-share"></span>
<span class="icon-text">Share</span> <span class="icon-text" i18n>Share</span>
</div> </div>
<div class="action-more" dropdown dropup="true" placement="right"> <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"> <ul *dropdownMenu class="dropdown-menu" id="more-menu" role="menu" aria-labelledby="single-button">
<li role="menuitem"> <li role="menuitem">
<a class="dropdown-item" title="Download the video" href="#" (click)="showDownloadModal($event)"> <a class="dropdown-item" i18n-title title="Download the video" href="#" (click)="showDownloadModal($event)">
<span class="icon icon-download"></span> Download <span class="icon icon-download"></span> <ng-container i18n>Download</ng-container>
</a> </a>
</li> </li>
<li *ngIf="isUserLoggedIn()" role="menuitem"> <li *ngIf="isUserLoggedIn()" role="menuitem">
<a class="dropdown-item" title="Report this video" href="#" (click)="showReportModal($event)"> <a class="dropdown-item" i18n-title title="Report this video" href="#" (click)="showReportModal($event)">
<span class="icon icon-alert"></span> Report <span class="icon icon-alert"></span> <ng-container i18n>Report</ng-container>
</a> </a>
</li> </li>
<li *ngIf="isVideoBlacklistable()" role="menuitem"> <li *ngIf="isVideoBlacklistable()" role="menuitem">
<a class="dropdown-item" title="Blacklist this video" href="#" (click)="blacklistVideo($event)"> <a class="dropdown-item" i18n-title title="Blacklist this video" href="#" (click)="blacklistVideo($event)">
<span class="icon icon-blacklist"></span> Blacklist <span class="icon icon-blacklist"></span> <ng-container i18n>Blacklist</ng-container>
</a> </a>
</li> </li>
<li *ngIf="isVideoUpdatable()" role="menuitem"> <li *ngIf="isVideoUpdatable()" role="menuitem">
<a class="dropdown-item" title="Update this video" href="#" [routerLink]="[ '/videos/update', video.uuid ]"> <a class="dropdown-item" i18n-title title="Update this video" href="#" [routerLink]="[ '/videos/update', video.uuid ]">
<span class="icon icon-edit"></span> Update <span class="icon icon-edit"></span> <ng-container i18n>Update</ng-container>
</a> </a>
</li> </li>
<li *ngIf="isVideoRemovable()" role="menuitem"> <li *ngIf="isVideoRemovable()" role="menuitem">
<a class="dropdown-item" title="Delete this video" href="#" (click)="removeVideo($event)"> <a class="dropdown-item" i18n-title title="Delete this video" href="#" (click)="removeVideo($event)">
<span class="icon icon-blacklist"></span> Delete <span class="icon icon-blacklist"></span> <ng-container i18n>Delete</ng-container>
</a> </a>
</li> </li>
</ul> </ul>
@ -109,20 +109,20 @@
<div class="video-info-description-html" [innerHTML]="videoHTMLDescription"></div> <div class="video-info-description-html" [innerHTML]="videoHTMLDescription"></div>
<div class="video-info-description-more" *ngIf="completeDescriptionShown === false && video.description?.length >= 250" (click)="showMoreDescription()"> <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> <span *ngIf="descriptionLoading === false" class="glyphicon glyphicon-menu-down"></span>
<my-loader class="description-loading" [loading]="descriptionLoading"></my-loader> <my-loader class="description-loading" [loading]="descriptionLoading"></my-loader>
</div> </div>
<div *ngIf="completeDescriptionShown === true" (click)="showLessDescription()" class="video-info-description-more"> <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> <span *ngIf="descriptionLoading === false" class="glyphicon glyphicon-menu-up"></span>
</div> </div>
</div> </div>
<div class="video-attributes"> <div class="video-attributes">
<div class="video-attribute"> <div class="video-attribute">
<span class="video-attribute-label"> <span i18n class="video-attribute-label">
Privacy Privacy
</span> </span>
<span class="video-attribute-value"> <span class="video-attribute-value">
@ -131,7 +131,7 @@
</div> </div>
<div class="video-attribute"> <div class="video-attribute">
<span class="video-attribute-label"> <span i18n class="video-attribute-label">
Category Category
</span> </span>
<span class="video-attribute-value"> <span class="video-attribute-value">
@ -140,7 +140,7 @@
</div> </div>
<div class="video-attribute"> <div class="video-attribute">
<span class="video-attribute-label"> <span i18n class="video-attribute-label">
Licence Licence
</span> </span>
<span class="video-attribute-value"> <span class="video-attribute-value">
@ -149,7 +149,7 @@
</div> </div>
<div class="video-attribute"> <div class="video-attribute">
<span class="video-attribute-label"> <span i18n class="video-attribute-label">
Language Language
</span> </span>
<span class="video-attribute-value"> <span class="video-attribute-value">
@ -158,7 +158,7 @@
</div> </div>
<div class="video-attribute"> <div class="video-attribute">
<span class="video-attribute-label"> <span i18n class="video-attribute-label">
Tags Tags
</span> </span>
@ -172,7 +172,7 @@
</div> </div>
<div class="other-videos"> <div class="other-videos">
<div class="title-page title-page-single"> <div i18n class="title-page title-page-single">
Other videos Other videos
</div> </div>
@ -184,13 +184,15 @@
<div class="privacy-concerns" *ngIf="hasAlreadyAcceptedPrivacyConcern === false"> <div class="privacy-concerns" *ngIf="hasAlreadyAcceptedPrivacyConcern === false">
<strong>Friendly Reminder:</strong> <strong i18n>Friendly Reminder:</strong>
<div class="privacy-concerns-text"> <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. <ng-container i18n>
<a title="Get more information" target="_blank" rel="noopener noreferrer" href="/about#p2p-privacy">More information</a> 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>
<div class="privacy-concerns-okay" (click)="acceptedPrivacyConcern()"> <div i18n class="privacy-concerns-okay" (click)="acceptedPrivacyConcern()">
OK OK
</div> </div>
</div> </div>

View file

@ -23,6 +23,7 @@ import { VideoReportComponent } from './modal/video-report.component'
import { VideoShareComponent } from './modal/video-share.component' import { VideoShareComponent } from './modal/video-share.component'
import { getVideojsOptions } from '../../../assets/player/peertube-player' import { getVideojsOptions } from '../../../assets/player/peertube-player'
import { ServerService } from '@app/core' import { ServerService } from '@app/core'
import { I18n } from '@ngx-translate/i18n-polyfill'
@Component({ @Component({
selector: 'my-video-watch', selector: 'my-video-watch',
@ -70,7 +71,8 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
private notificationsService: NotificationsService, private notificationsService: NotificationsService,
private markdownService: MarkdownService, private markdownService: MarkdownService,
private zone: NgZone, private zone: NgZone,
private redirectService: RedirectService private redirectService: RedirectService,
private i18n: I18n
) {} ) {}
get user () { get user () {
@ -153,17 +155,20 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
async blacklistVideo (event: Event) { async blacklistVideo (event: Event) {
event.preventDefault() 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 if (res === false) return
this.videoBlacklistService.blacklistVideo(this.video.id) this.videoBlacklistService.blacklistVideo(this.video.id)
.subscribe( .subscribe(
status => { 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() 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 => { error => {
this.descriptionLoading = false 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) { async removeVideo (event: Event) {
event.preventDefault() 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 if (res === false) return
this.videoService.removeVideo(this.video.id) this.videoService.removeVideo(this.video.id)
.subscribe( .subscribe(
status => { 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. // Go back to the video-list.
this.redirectService.redirectToHomepage() 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 () { 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) { private handleError (err: any) {
@ -298,12 +309,12 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
let message = '' let message = ''
if (errorMessage.indexOf('http error') !== -1) { 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 { } else {
message = errorMessage message = errorMessage
} }
this.notificationsService.error('Error', message) this.notificationsService.error(this.i18n('Error'), message)
} }
private checkUserRating () { 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())) { if (this.video.isVideoNSFWForUser(this.user, this.serverService.getConfig())) {
const res = await this.confirmService.confirm( const res = await this.confirmService.confirm(
'This video contains mature or explicit content. Are you sure you want to watch it?', this.i18n('This video contains mature or explicit content. Are you sure you want to watch it?'),
'Mature or explicit content' this.i18n('Mature or explicit content')
) )
if (res === false) return this.redirectService.redirectToHomepage() if (res === false) return this.redirectService.redirectToHomepage()
} }
@ -399,7 +410,7 @@ export class VideoWatchComponent implements OnInit, OnDestroy {
this.updateVideoRating(this.userRating, nextRating) this.updateVideoRating(this.userRating, nextRating)
this.userRating = nextRating this.userRating = nextRating
}, },
err => this.notificationsService.error('Error', err.message) err => this.notificationsService.error(this.i18n('Error'), err.message)
) )
} }

View file

@ -8,6 +8,7 @@ import { AbstractVideoList } from '../../shared/video/abstract-video-list'
import { VideoSortField } from '../../shared/video/sort-field.type' import { VideoSortField } from '../../shared/video/sort-field.type'
import { VideoService } from '../../shared/video/video.service' import { VideoService } from '../../shared/video/video.service'
import { VideoFilter } from '../../../../../shared/models/videos/video-query.type' import { VideoFilter } from '../../../../../shared/models/videos/video-query.type'
import { I18n } from '@ngx-translate/i18n-polyfill'
@Component({ @Component({
selector: 'my-videos-local', selector: 'my-videos-local',
@ -15,18 +16,23 @@ import { VideoFilter } from '../../../../../shared/models/videos/video-query.typ
templateUrl: '../../shared/video/abstract-video-list.html' templateUrl: '../../shared/video/abstract-video-list.html'
}) })
export class VideoLocalComponent extends AbstractVideoList implements OnInit, OnDestroy { export class VideoLocalComponent extends AbstractVideoList implements OnInit, OnDestroy {
titlePage = 'Local videos' titlePage: string
currentRoute = '/videos/local' currentRoute = '/videos/local'
sort = '-publishedAt' as VideoSortField sort = '-publishedAt' as VideoSortField
filter: VideoFilter = 'local' filter: VideoFilter = 'local'
constructor (protected router: Router, constructor (
protected route: ActivatedRoute, protected router: Router,
protected notificationsService: NotificationsService, protected route: ActivatedRoute,
protected authService: AuthService, protected notificationsService: NotificationsService,
protected location: Location, protected authService: AuthService,
private videoService: VideoService) { protected location: Location,
private videoService: VideoService,
private i18n: I18n
) {
super() super()
this.titlePage = i18n('Local videos')
} }
ngOnInit () { ngOnInit () {

View file

@ -7,6 +7,7 @@ import { AuthService } from '../../core/auth'
import { AbstractVideoList } from '../../shared/video/abstract-video-list' import { AbstractVideoList } from '../../shared/video/abstract-video-list'
import { VideoSortField } from '../../shared/video/sort-field.type' import { VideoSortField } from '../../shared/video/sort-field.type'
import { VideoService } from '../../shared/video/video.service' import { VideoService } from '../../shared/video/video.service'
import { I18n } from '@ngx-translate/i18n-polyfill'
@Component({ @Component({
selector: 'my-videos-recently-added', selector: 'my-videos-recently-added',
@ -14,17 +15,22 @@ import { VideoService } from '../../shared/video/video.service'
templateUrl: '../../shared/video/abstract-video-list.html' templateUrl: '../../shared/video/abstract-video-list.html'
}) })
export class VideoRecentlyAddedComponent extends AbstractVideoList implements OnInit, OnDestroy { export class VideoRecentlyAddedComponent extends AbstractVideoList implements OnInit, OnDestroy {
titlePage = 'Recently added' titlePage: string
currentRoute = '/videos/recently-added' currentRoute = '/videos/recently-added'
sort: VideoSortField = '-publishedAt' sort: VideoSortField = '-publishedAt'
constructor (protected router: Router, constructor (
protected route: ActivatedRoute, protected router: Router,
protected location: Location, protected route: ActivatedRoute,
protected notificationsService: NotificationsService, protected location: Location,
protected authService: AuthService, protected notificationsService: NotificationsService,
private videoService: VideoService) { protected authService: AuthService,
private videoService: VideoService,
private i18n: I18n
) {
super() super()
this.titlePage = i18n('Recently added')
} }
ngOnInit () { ngOnInit () {

View file

@ -8,6 +8,7 @@ import { Subscription } from 'rxjs'
import { AuthService } from '../../core/auth' import { AuthService } from '../../core/auth'
import { AbstractVideoList } from '../../shared/video/abstract-video-list' import { AbstractVideoList } from '../../shared/video/abstract-video-list'
import { VideoService } from '../../shared/video/video.service' import { VideoService } from '../../shared/video/video.service'
import { I18n } from '@ngx-translate/i18n-polyfill'
@Component({ @Component({
selector: 'my-videos-search', selector: 'my-videos-search',
@ -15,7 +16,7 @@ import { VideoService } from '../../shared/video/video.service'
templateUrl: '../../shared/video/abstract-video-list.html' templateUrl: '../../shared/video/abstract-video-list.html'
}) })
export class VideoSearchComponent extends AbstractVideoList implements OnInit, OnDestroy { export class VideoSearchComponent extends AbstractVideoList implements OnInit, OnDestroy {
titlePage = 'Search' titlePage: string
currentRoute = '/videos/search' currentRoute = '/videos/search'
loadOnInit = false loadOnInit = false
@ -24,15 +25,19 @@ export class VideoSearchComponent extends AbstractVideoList implements OnInit, O
} }
private subActivatedRoute: Subscription private subActivatedRoute: Subscription
constructor (protected router: Router, constructor (
protected route: ActivatedRoute, protected router: Router,
protected notificationsService: NotificationsService, protected route: ActivatedRoute,
protected authService: AuthService, protected notificationsService: NotificationsService,
protected location: Location, protected authService: AuthService,
private videoService: VideoService, protected location: Location,
private redirectService: RedirectService private videoService: VideoService,
private redirectService: RedirectService,
private i18n: I18n
) { ) {
super() super()
this.titlePage = i18n('Search')
} }
ngOnInit () { ngOnInit () {

View file

@ -7,6 +7,7 @@ import { AuthService } from '../../core/auth'
import { AbstractVideoList } from '../../shared/video/abstract-video-list' import { AbstractVideoList } from '../../shared/video/abstract-video-list'
import { VideoSortField } from '../../shared/video/sort-field.type' import { VideoSortField } from '../../shared/video/sort-field.type'
import { VideoService } from '../../shared/video/video.service' import { VideoService } from '../../shared/video/video.service'
import { I18n } from '@ngx-translate/i18n-polyfill'
@Component({ @Component({
selector: 'my-videos-trending', selector: 'my-videos-trending',
@ -14,17 +15,22 @@ import { VideoService } from '../../shared/video/video.service'
templateUrl: '../../shared/video/abstract-video-list.html' templateUrl: '../../shared/video/abstract-video-list.html'
}) })
export class VideoTrendingComponent extends AbstractVideoList implements OnInit, OnDestroy { export class VideoTrendingComponent extends AbstractVideoList implements OnInit, OnDestroy {
titlePage = 'Trending' titlePage: string
currentRoute = '/videos/trending' currentRoute = '/videos/trending'
defaultSort: VideoSortField = '-views' defaultSort: VideoSortField = '-views'
constructor (protected router: Router, constructor (
protected route: ActivatedRoute, protected router: Router,
protected notificationsService: NotificationsService, protected route: ActivatedRoute,
protected authService: AuthService, protected notificationsService: NotificationsService,
protected location: Location, protected authService: AuthService,
private videoService: VideoService) { protected location: Location,
private videoService: VideoService,
private i18n: I18n
) {
super() super()
this.titlePage = i18n('Trending')
} }
ngOnInit () { ngOnInit () {

View 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 :&apos;(</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>
&lt;x id="INTERPOLATION" equiv-text="{{ video.publishedAt | myFromNow }}"/&gt; - &lt;x id="INTERPOLATION_1" equiv-text="{{ video.views | myNumberFormatter }}"/&gt; 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 &lt;strong&gt;@&lt;x id="INTERPOLATION" equiv-text="{{video.account.displayName}}"/&gt;@&lt;x id="INTERPOLATION_1" equiv-text="{{video.account.host}}"/&gt;&lt;/strong&gt; and subscribe there. Subscription as a PeerTube user is being worked on in &lt;a href=&apos;https://github.com/Chocobozzz/PeerTube/issues/470&apos;&gt;#470&lt;/a&gt;.</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 &lt;x id="INTERPOLATION" equiv-text="{{ video.by }}"/&gt;</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 &lt;x id="INTERPOLATION" equiv-text="{{ videoName }}"/&gt; 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 &lt;x id="INTERPOLATION" equiv-text="{{ videoName }}"/&gt; 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>&lt;x id="INTERPOLATION" equiv-text="{{ likesNumber }}"/&gt; likes / &lt;x id="INTERPOLATION_1" equiv-text="{{ dislikesNumber }}"/&gt; 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>

View 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 &lt;strong&gt;@<x id="INTERPOLATION" equiv-text="{{video.account.displayName}}"/>@<x id="INTERPOLATION_1" equiv-text="{{video.account.host}}"/>&lt;/strong&gt; and subscribe there. Subscription as a PeerTube user is being worked on in &lt;a href='https://github.com/Chocobozzz/PeerTube/issues/470'&gt;#470&lt;/a&gt;.</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 &lt;strong&gt;@<x id="INTERPOLATION" equiv-text="{{video.account.displayName}}"/>@<x id="INTERPOLATION_1" equiv-text="{{video.account.host}}"/>&lt;/strong&gt; and subscribe there. Subscription as a PeerTube user is being worked on in &lt;a href='https://github.com/Chocobozzz/PeerTube/issues/470'&gt;#470&lt;/a&gt;.</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>

View file

@ -242,6 +242,14 @@
dependencies: dependencies:
tslib "~1.9.0" 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": "@nodelib/fs.stat@^1.0.1":
version "1.1.0" version "1.1.0"
resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-1.1.0.tgz#50c1e2260ac0ed9439a181de3725a0168d59c48a" 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" once "^1.3.0"
path-is-absolute "^1.0.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: glob@^5.0.15:
version "5.0.15" version "5.0.15"
resolved "https://registry.yarnpkg.com/glob/-/glob-5.0.15.tgz#1bc936b9e02f4a603fcc222ecf7633d30b8b93b1" resolved "https://registry.yarnpkg.com/glob/-/glob-5.0.15.tgz#1bc936b9e02f4a603fcc222ecf7633d30b8b93b1"
@ -4209,17 +4228,6 @@ glob@^6.0.4:
once "^1.3.0" once "^1.3.0"
path-is-absolute "^1.0.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: global-modules@^1.0.0:
version "1.0.0" version "1.0.0"
resolved "https://registry.yarnpkg.com/global-modules/-/global-modules-1.0.0.tgz#6d770f0eb523ac78164d72b5e71a8877265cc3ea" resolved "https://registry.yarnpkg.com/global-modules/-/global-modules-1.0.0.tgz#6d770f0eb523ac78164d72b5e71a8877265cc3ea"
@ -10594,12 +10602,35 @@ yargs-parser@^7.0.0:
dependencies: dependencies:
camelcase "^4.1.0" 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: yargs-parser@^9.0.2:
version "9.0.2" version "9.0.2"
resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-9.0.2.tgz#9ccf6a43460fe4ed40a9bb68f48d43b8a68cc077" resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-9.0.2.tgz#9ccf6a43460fe4ed40a9bb68f48d43b8a68cc077"
dependencies: dependencies:
camelcase "^4.1.0" 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: yargs@11.0.0:
version "11.0.0" version "11.0.0"
resolved "https://registry.yarnpkg.com/yargs/-/yargs-11.0.0.tgz#c052931006c5eee74610e5fc0354bedfd08a201b" resolved "https://registry.yarnpkg.com/yargs/-/yargs-11.0.0.tgz#c052931006c5eee74610e5fc0354bedfd08a201b"

View file

@ -29,6 +29,7 @@
"danger:clean:dev": "scripty", "danger:clean:dev": "scripty",
"danger:clean:prod": "scripty", "danger:clean:prod": "scripty",
"danger:clean:modules": "scripty", "danger:clean:modules": "scripty",
"i18n:generate": "scripty",
"reset-password": "node ./dist/scripts/reset-password.js", "reset-password": "node ./dist/scripts/reset-password.js",
"play": "scripty", "play": "scripty",
"dev": "scripty", "dev": "scripty",

View file

@ -6,5 +6,19 @@ cd client
rm -rf ./dist ./compiled 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 NODE_ENV=production npm run webpack -- --config webpack/webpack.video-embed.js --mode production

11
scripts/i18n/generate.sh Executable file
View 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=\([^\/]\+\?\)\/>/\&lt;x id=\1\/\&gt;/g' src/locale/source/messages_en_US.xml

7
scripts/i18n/pull-hook.sh Executable file
View 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/\&lt;x id=\([^\/]\+\?\)\/\&gt;/<x id=\1\/>/g' client/src/locale/target/*

View file

@ -57,7 +57,7 @@ git commit package.json client/package.json -m "Bumped to version $version"
git tag -s -a "$version" -m "$version" git tag -s -a "$version" -m "$version"
npm run build npm run build
rm "./client/dist/stats.json" rm "./client/dist/en-US/stats.json"
# Creating the archives # Creating the archives
( (

View file

@ -12,7 +12,6 @@ import * as bodyParser from 'body-parser'
import * as express from 'express' import * as express from 'express'
import * as http from 'http' import * as http from 'http'
import * as morgan from 'morgan' import * as morgan from 'morgan'
import * as path from 'path'
import * as bitTorrentTracker from 'bittorrent-tracker' import * as bitTorrentTracker from 'bittorrent-tracker'
import * as cors from 'cors' import * as cors from 'cors'
import { Server as WebSocketServer } from 'ws' import { Server as WebSocketServer } from 'ws'
@ -156,20 +155,11 @@ app.use('/', activityPubRouter)
app.use('/', feedsRouter) app.use('/', feedsRouter)
app.use('/', webfingerRouter) app.use('/', webfingerRouter)
// Client files
app.use('/', clientsRouter)
// Static files // Static files
app.use('/', staticRouter) app.use('/', staticRouter)
// Always serve index client page (the client is a single page application, let it handle routing) // Client files, last valid routes!
app.use('/*', function (req, res) { app.use('/', clientsRouter)
if (req.accepts(ACCEPT_HEADERS) === 'html') {
return res.sendFile(path.join(__dirname, '../client/dist/index.html'))
}
return res.status(404).end()
})
// ----------- Errors ----------- // ----------- Errors -----------

View file

@ -3,17 +3,24 @@ import * as express from 'express'
import { join } from 'path' import { join } from 'path'
import * as validator from 'validator' import * as validator from 'validator'
import { escapeHTML, readFileBufferPromise, root } from '../helpers/core-utils' 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 { asyncMiddleware } from '../middlewares'
import { VideoModel } from '../models/video/video' import { VideoModel } from '../models/video/video'
import { VideoPrivacy } from '../../shared/models/videos' import { VideoPrivacy } from '../../shared/models/videos'
import { I18N_LOCALES, is18nLocale, getDefaultLocale } from '../../shared/models'
const clientsRouter = express.Router() const clientsRouter = express.Router()
const distPath = join(root(), 'client', 'dist') const distPath = join(root(), 'client', 'dist')
const assetsImagesPath = join(root(), 'client', 'dist', 'assets', 'images') const assetsImagesPath = join(root(), 'client', 'dist', 'assets', 'images')
const embedPath = join(distPath, 'standalone', 'videos', 'embed.html') const embedPath = join(distPath, 'standalone', 'videos', 'embed.html')
const indexPath = join(distPath, 'index.html')
// Special route that add OpenGraph and oEmbed tags // Special route that add OpenGraph and oEmbed tags
// Do not use a template engine for a so little thing // 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) 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 { 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) { function addOpenGraphAndOEmbedTags (htmlStringPage: string, video: VideoModel) {
const previewUrl = CONFIG.WEBSERVER.URL + STATIC_PATHS.PREVIEWS + video.getPreviewName() const previewUrl = CONFIG.WEBSERVER.URL + STATIC_PATHS.PREVIEWS + video.getPreviewName()
const videoUrl = CONFIG.WEBSERVER.URL + '/videos/watch/' + video.uuid 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)) { } else if (validator.isInt(videoId)) {
videoPromise = VideoModel.loadAndPopulateAccountAndServerAndTags(+videoId) videoPromise = VideoModel.loadAndPopulateAccountAndServerAndTags(+videoId)
} else { } else {
return res.sendFile(indexPath) return res.sendFile(getIndexPath(req))
} }
let [ file, video ] = await Promise.all([ let [ file, video ] = await Promise.all([
readFileBufferPromise(indexPath), readFileBufferPromise(getIndexPath(req)),
videoPromise videoPromise
]) ])
const html = file.toString() const html = file.toString()
// Let Angular application handle errors // 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) const htmlStringPageWithTags = addOpenGraphAndOEmbedTags(html, video)
res.set('Content-Type', 'text/html; charset=UTF-8').send(htmlStringPageWithTags) res.set('Content-Type', 'text/html; charset=UTF-8').send(htmlStringPageWithTags)

View 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('-', '_')
}

View file

@ -0,0 +1 @@
export * from './i18n'

View file

@ -3,6 +3,7 @@ export * from './activitypub'
export * from './users' export * from './users'
export * from './videos' export * from './videos'
export * from './feeds' export * from './feeds'
export * from './i18n'
export * from './server/job.model' export * from './server/job.model'
export * from './oauth-client-local.model' export * from './oauth-client-local.model'
export * from './result-list.model' export * from './result-list.model'

View file

@ -1237,7 +1237,7 @@ chalk@^1.0.0, chalk@^1.1.1, chalk@^1.1.3:
strip-ansi "^3.0.0" strip-ansi "^3.0.0"
supports-color "^2.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" version "2.4.1"
resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.1.tgz#18c49ab16a037b6eb0152cc83e3471338215b66e" resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.1.tgz#18c49ab16a037b6eb0152cc83e3471338215b66e"
dependencies: dependencies:
@ -1517,7 +1517,7 @@ command-exists@^1.2.2:
version "1.2.6" version "1.2.6"
resolved "https://registry.yarnpkg.com/command-exists/-/command-exists-1.2.6.tgz#577f8e5feb0cb0f159cd557a51a9be1bdd76e09e" 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" version "2.15.1"
resolved "https://registry.yarnpkg.com/commander/-/commander-2.15.1.tgz#df46e867d0fc2aec66a34662b406a9ccafff5b0f" 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: dependencies:
is-arrayish "^0.2.1" 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: es5-ext@^0.10.14, es5-ext@^0.10.35, es5-ext@^0.10.9, es5-ext@~0.10.14:
version "0.10.43" version "0.10.43"
resolved "https://registry.yarnpkg.com/es5-ext/-/es5-ext-0.10.43.tgz#c705e645253210233a270869aa463a2333b7ca64" 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" version "3.0.2"
resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-3.0.2.tgz#9866df395102130e38f7f996bceb65443209c25b" 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" version "3.11.0"
resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.11.0.tgz#597c1a8bd57152f26d622ce4117851a51f5ebaef" resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.11.0.tgz#597c1a8bd57152f26d622ce4117851a51f5ebaef"
dependencies: dependencies:
@ -6741,18 +6735,6 @@ sass-graph@^2.2.4:
scss-tokenizer "^0.2.3" scss-tokenizer "^0.2.3"
yargs "^7.0.0" 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: sass-lint@^1.12.1:
version "1.12.1" version "1.12.1"
resolved "https://registry.yarnpkg.com/sass-lint/-/sass-lint-1.12.1.tgz#630f69c216aa206b8232fb2aa907bdf3336b6d83" 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: dependencies:
amdefine ">=0.0.4" 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: source-map@0.5.x, source-map@^0.5.6, source-map@~0.5.1:
version "0.5.7" version "0.5.7"
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" 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" version "1.3.7"
resolved "https://registry.yarnpkg.com/stack-chain/-/stack-chain-1.3.7.tgz#d192c9ff4ea6a22c94c4dd459171e3f00cea1285" 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: stack-trace@0.0.x:
version "0.0.10" version "0.0.10"
resolved "https://registry.yarnpkg.com/stack-trace/-/stack-trace-0.0.10.tgz#547c70b347e8d32b4e108ea1a2a159e5fdde19c0" 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" version "1.0.1"
resolved "https://registry.yarnpkg.com/stack-utils/-/stack-utils-1.0.1.tgz#d4f33ab54e8e38778b0ca5cfd3b3afb12db68620" 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: staged-git-files@1.1.1:
version "1.1.1" version "1.1.1"
resolved "https://registry.yarnpkg.com/staged-git-files/-/staged-git-files-1.1.1.tgz#37c2218ef0d6d26178b1310719309a16a59f8f7b" resolved "https://registry.yarnpkg.com/staged-git-files/-/staged-git-files-1.1.1.tgz#37c2218ef0d6d26178b1310719309a16a59f8f7b"

15
zanata.xml Normal file
View 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>