Verified Commit 989e526a authored by Chocobozzz's avatar Chocobozzz
Browse files

Prepare i18n files

parent 1dd59831
......@@ -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",
......
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()
}
}
......
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