From e945b184a0f29b47c33bbd05578f3493ca9c8e6c Mon Sep 17 00:00:00 2001 From: Chocobozzz <me@florianbigard.com> Date: Wed, 6 Jun 2018 14:23:40 +0200 Subject: Localize player --- .../videos/+video-watch/video-watch.component.ts | 19 +- client/src/assets/player/peertube-link-button.ts | 2 +- client/src/assets/player/peertube-player.ts | 67 ++-- .../src/assets/player/peertube-videojs-plugin.ts | 10 +- client/src/assets/player/resolution-menu-button.ts | 7 +- client/src/assets/player/settings-menu-button.ts | 2 +- client/src/assets/player/settings-menu-item.ts | 4 +- client/src/assets/player/utils.ts | 2 + client/src/assets/player/webtorrent-info-button.ts | 7 +- client/src/locale/source/player_en_US.xml | 378 ++++++++++++++++++++ client/src/locale/source/videojs_en_US.json | 85 +++++ client/src/locale/target/player_fr.json | 1 + client/src/locale/target/player_fr.xml | 379 +++++++++++++++++++++ client/src/standalone/videos/embed.ts | 12 +- package.json | 3 + scripts/i18n/create-custom-files.ts | 49 +++ scripts/i18n/generate.sh | 6 +- scripts/i18n/pull-hook.sh | 6 +- scripts/i18n/xliff2json.ts | 42 +++ scripts/watch/server.sh | 5 + server/controllers/client.ts | 8 + shared/models/i18n/i18n.ts | 4 + yarn.lock | 12 + 23 files changed, 1049 insertions(+), 61 deletions(-) create mode 100644 client/src/locale/source/player_en_US.xml create mode 100644 client/src/locale/source/videojs_en_US.json create mode 100644 client/src/locale/target/player_fr.json create mode 100644 client/src/locale/target/player_fr.xml create mode 100755 scripts/i18n/create-custom-files.ts create mode 100755 scripts/i18n/xliff2json.ts diff --git a/client/src/app/videos/+video-watch/video-watch.component.ts b/client/src/app/videos/+video-watch/video-watch.component.ts index 23d74494c..d3e16c4cf 100644 --- a/client/src/app/videos/+video-watch/video-watch.component.ts +++ b/client/src/app/videos/+video-watch/video-watch.component.ts @@ -1,5 +1,5 @@ import { catchError } from 'rxjs/operators' -import { Component, ElementRef, NgZone, OnDestroy, OnInit, ViewChild } from '@angular/core' +import { Component, ElementRef, LOCALE_ID, NgZone, OnDestroy, OnInit, ViewChild, Inject } from '@angular/core' import { ActivatedRoute, Router } from '@angular/router' import { RedirectService } from '@app/core/routing/redirect.service' import { peertubeLocalStorage } from '@app/shared/misc/peertube-local-storage' @@ -21,9 +21,10 @@ import { MarkdownService } from '../shared' import { VideoDownloadComponent } from './modal/video-download.component' import { VideoReportComponent } from './modal/video-report.component' import { VideoShareComponent } from './modal/video-share.component' -import { getVideojsOptions } from '../../../assets/player/peertube-player' +import { getVideojsOptions, loadLocale, addContextMenu } from '../../../assets/player/peertube-player' import { ServerService } from '@app/core' import { I18n } from '@ngx-translate/i18n-polyfill' +import { environment } from '../../../environments/environment' @Component({ selector: 'my-video-watch', @@ -54,6 +55,7 @@ export class VideoWatchComponent implements OnInit, OnDestroy { likesBarTooltipText = '' hasAlreadyAcceptedPrivacyConcern = false + private videojsLocaleLoaded = false private otherVideos: Video[] = [] private paramsSub: Subscription @@ -72,7 +74,8 @@ export class VideoWatchComponent implements OnInit, OnDestroy { private markdownService: MarkdownService, private zone: NgZone, private redirectService: RedirectService, - private i18n: I18n + private i18n: I18n, + @Inject(LOCALE_ID) private localeId: string ) {} get user () { @@ -365,7 +368,6 @@ export class VideoWatchComponent implements OnInit, OnDestroy { inactivityTimeout: 2500, videoFiles: this.video.files, playerElement: this.playerElement, - videoEmbedUrl: this.video.embedUrl, videoViewUrl: this.videoService.getVideoViewUrl(this.video.uuid), videoDuration: this.video.duration, enableHotkeys: true, @@ -374,11 +376,18 @@ export class VideoWatchComponent implements OnInit, OnDestroy { startTime }) + if (this.videojsLocaleLoaded === false) { + await loadLocale(environment.apiUrl, videojs, environment.production === true ? this.localeId : 'fr') + this.videojsLocaleLoaded = true + } + const self = this - this.zone.runOutsideAngular(() => { + this.zone.runOutsideAngular(async () => { videojs(this.playerElement, videojsOptions, function () { self.player = this this.on('customError', (event, data) => self.handleError(data.err)) + + addContextMenu(self.player, self.video.embedUrl) }) }) diff --git a/client/src/assets/player/peertube-link-button.ts b/client/src/assets/player/peertube-link-button.ts index a13815d61..26f8b9d73 100644 --- a/client/src/assets/player/peertube-link-button.ts +++ b/client/src/assets/player/peertube-link-button.ts @@ -24,7 +24,7 @@ class PeerTubeLinkButton extends Button { const el = videojsUntyped.dom.createEl('a', { href: buildVideoLink(), innerHTML: 'PeerTube', - title: 'Go to the video page', + title: this.player_.localize('Go to the video page'), className: 'vjs-peertube-link', target: '_blank' }) diff --git a/client/src/assets/player/peertube-player.ts b/client/src/assets/player/peertube-player.ts index d204b9703..b604097fa 100644 --- a/client/src/assets/player/peertube-player.ts +++ b/client/src/assets/player/peertube-player.ts @@ -12,6 +12,7 @@ import './peertube-videojs-plugin' import './peertube-load-progress-bar' import { videojsUntyped } from './peertube-videojs-typings' import { buildVideoEmbed, buildVideoLink, copyToClipboard } from './utils' +import { is18nLocale, isDefaultLocale } from '../../../../shared/models/i18n/i18n' // Change 'Playback Rate' to 'Speed' (smaller for our settings menu) videojsUntyped.getComponent('PlaybackRateMenuButton').prototype.controlText_ = 'Speed' @@ -20,7 +21,6 @@ function getVideojsOptions (options: { autoplay: boolean, playerElement: HTMLVideoElement, videoViewUrl: string, - videoEmbedUrl: string, videoDuration: number, videoFiles: VideoFile[], enableHotkeys: boolean, @@ -43,29 +43,6 @@ function getVideojsOptions (options: { videoViewUrl: options.videoViewUrl, videoDuration: options.videoDuration, startTime: options.startTime - }, - contextmenuUI: { - content: [ - { - label: 'Copy the video URL', - listener: function () { - copyToClipboard(buildVideoLink()) - } - }, - { - label: 'Copy the video URL at the current time', - listener: function () { - const player = this - copyToClipboard(buildVideoLink(player.currentTime())) - } - }, - { - label: 'Copy embed code', - listener: () => { - copyToClipboard(buildVideoEmbed(options.videoEmbedUrl)) - } - } - ] } }, controlBar: { @@ -135,4 +112,44 @@ function getControlBarChildren (options: { return children } -export { getVideojsOptions } +function addContextMenu (player: any, videoEmbedUrl: string) { + console.log(videoEmbedUrl) + + player.contextmenuUI({ + content: [ + { + label: player.localize('Copy the video URL'), + listener: function () { + copyToClipboard(buildVideoLink()) + } + }, + { + label: player.localize('Copy the video URL at the current time'), + listener: function () { + const player = this + copyToClipboard(buildVideoLink(player.currentTime())) + } + }, + { + label: player.localize('Copy embed code'), + listener: () => { + copyToClipboard(buildVideoEmbed(videoEmbedUrl)) + } + } + ] + }) +} + +function loadLocale (serverUrl: string, videojs: any, locale: string) { + if (!is18nLocale(locale) || isDefaultLocale(locale)) return undefined + + return fetch(serverUrl + '/client/locales/' + locale + '/player.json') + .then(res => res.json()) + .then(json => videojs.addLanguage(locale, json)) +} + +export { + loadLocale, + getVideojsOptions, + addContextMenu +} diff --git a/client/src/assets/player/peertube-videojs-plugin.ts b/client/src/assets/player/peertube-videojs-plugin.ts index 79df42a53..68e98f170 100644 --- a/client/src/assets/player/peertube-videojs-plugin.ts +++ b/client/src/assets/player/peertube-videojs-plugin.ts @@ -4,15 +4,7 @@ import { VideoFile } from '../../../../shared/models/videos/video.model' import { renderVideo } from './video-renderer' import './settings-menu-button' import { PeertubePluginOptions, VideoJSComponentInterface, videojsUntyped } from './peertube-videojs-typings' -import { - getAverageBandwidth, - getStoredMute, - getStoredVolume, - isMobile, - saveAverageBandwidth, - saveMuteInStore, - saveVolumeInStore -} from './utils' +import { getAverageBandwidth, getStoredMute, getStoredVolume, saveAverageBandwidth, saveMuteInStore, saveVolumeInStore } from './utils' import minBy from 'lodash-es/minBy' import maxBy from 'lodash-es/maxBy' import * as CacheChunkStore from 'cache-chunk-store' diff --git a/client/src/assets/player/resolution-menu-button.ts b/client/src/assets/player/resolution-menu-button.ts index 2efc8de69..d317a5efc 100644 --- a/client/src/assets/player/resolution-menu-button.ts +++ b/client/src/assets/player/resolution-menu-button.ts @@ -8,10 +8,7 @@ class ResolutionMenuButton extends MenuButton { label: HTMLElement constructor (player: videojs.Player, options) { - options.label = 'Quality' super(player, options) - - this.controlText_ = 'Quality' this.player = player player.peertube().on('videoFileUpdate', () => this.updateLabel()) @@ -51,7 +48,7 @@ class ResolutionMenuButton extends MenuButton { this.player_, { id: -1, - label: 'Auto', + label: this.player_.localize('Auto'), src: null } )) @@ -77,4 +74,6 @@ class ResolutionMenuButton extends MenuButton { return this.player_.peertube().getCurrentResolutionLabel() } } +ResolutionMenuButton.prototype.controlText_ = 'Quality' + MenuButton.registerComponent('ResolutionMenuButton', ResolutionMenuButton) diff --git a/client/src/assets/player/settings-menu-button.ts b/client/src/assets/player/settings-menu-button.ts index bf6ac145a..b51c52506 100644 --- a/client/src/assets/player/settings-menu-button.ts +++ b/client/src/assets/player/settings-menu-button.ts @@ -275,7 +275,7 @@ class SettingsDialog extends Component { } -SettingsButton.prototype.controlText_ = 'Settings Button' +SettingsButton.prototype.controlText_ = 'Settings' Component.registerComponent('SettingsButton', SettingsButton) Component.registerComponent('SettingsDialog', SettingsDialog) diff --git a/client/src/assets/player/settings-menu-item.ts b/client/src/assets/player/settings-menu-item.ts index 048c88533..f595fd459 100644 --- a/client/src/assets/player/settings-menu-item.ts +++ b/client/src/assets/player/settings-menu-item.ts @@ -132,7 +132,7 @@ class SettingsMenuItem extends MenuItem { const button = this.subMenu.menu.addChild('MenuItem', {}, 0) button.name_ = 'BackButton' button.addClass('vjs-back-button') - button.el_.innerHTML = this.subMenu.controlText_ + button.el_.innerHTML = this.player_.localize(this.subMenu.controlText_) } /** @@ -201,7 +201,7 @@ class SettingsMenuItem extends MenuItem { saveUpdateLabel.call(this.subMenu) } - this.settingsSubMenuTitleEl_.innerHTML = this.subMenu.controlText_ + this.settingsSubMenuTitleEl_.innerHTML = this.player_.localize(this.subMenu.controlText_) this.settingsSubMenuEl_.appendChild(this.subMenu.menu.el_) this.panelChildEl.appendChild(this.settingsSubMenuEl_) this.update() diff --git a/client/src/assets/player/utils.ts b/client/src/assets/player/utils.ts index 487b3a1be..ce7aaea2a 100644 --- a/client/src/assets/player/utils.ts +++ b/client/src/assets/player/utils.ts @@ -1,3 +1,5 @@ +import { is18nLocale, isDefaultLocale } from '../../../../shared/models/i18n/i18n' + function toTitleCase (str: string) { return str.charAt(0).toUpperCase() + str.slice(1) } diff --git a/client/src/assets/player/webtorrent-info-button.ts b/client/src/assets/player/webtorrent-info-button.ts index baeb22b64..10945c665 100644 --- a/client/src/assets/player/webtorrent-info-button.ts +++ b/client/src/assets/player/webtorrent-info-button.ts @@ -60,13 +60,8 @@ class WebtorrentInfoButton extends Button { className: 'peers-number', textContent: 'HTTP' }) - const subDivFallbackText = videojsUntyped.dom.createEl('span', { - className: 'peers-text', - textContent: ' fallback' - }) subDivHttp.appendChild(subDivHttpText) - subDivHttp.appendChild(subDivFallbackText) div.appendChild(subDivHttp) this.player_.peertube().on('torrentInfo', (event, data) => { @@ -89,7 +84,7 @@ class WebtorrentInfoButton extends Button { uploadSpeedUnit.textContent = ' ' + uploadSpeed[ 1 ] peersNumber.textContent = numPeers - peersText.textContent = ' peers' + peersText.textContent = ' ' + this.player_.localize('peers') subDivHttp.className = 'vjs-peertube-hidden' subDivWebtorrent.className = 'vjs-peertube-displayed' diff --git a/client/src/locale/source/player_en_US.xml b/client/src/locale/source/player_en_US.xml new file mode 100644 index 000000000..5bb6afdf7 --- /dev/null +++ b/client/src/locale/source/player_en_US.xml @@ -0,0 +1,378 @@ +<xliff xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="urn:oasis:names:tc:xliff:document:1.2 http://docs.oasis-open.org/xliff/v1.2/os/xliff-core-1.2-strict.xsd" xmlns="urn:oasis:names:tc:xliff:document:1.2" version="1.2"> + <file original="namespace1" datatype="plaintext" source-language="undefined" target-language="undefined"> + <body> + <trans-unit id="Audio Player"> + <source>Audio Player</source> + <target>undefined</target> + </trans-unit> + <trans-unit id="Video Player"> + <source>Video Player</source> + <target>undefined</target> + </trans-unit> + <trans-unit id="Play"> + <source>Play</source> + <target>undefined</target> + </trans-unit> + <trans-unit id="Pause"> + <source>Pause</source> + <target>undefined</target> + </trans-unit> + <trans-unit id="Replay"> + <source>Replay</source> + <target>undefined</target> + </trans-unit> + <trans-unit id="Current Time"> + <source>Current Time</source> + <target>undefined</target> + </trans-unit> + <trans-unit id="Duration"> + <source>Duration</source> + <target>undefined</target> + </trans-unit> + <trans-unit id="Remaining Time"> + <source>Remaining Time</source> + <target>undefined</target> + </trans-unit> + <trans-unit id="Stream Type"> + <source>Stream Type</source> + <target>undefined</target> + </trans-unit> + <trans-unit id="LIVE"> + <source>LIVE</source> + <target>undefined</target> + </trans-unit> + <trans-unit id="Loaded"> + <source>Loaded</source> + <target>undefined</target> + </trans-unit> + <trans-unit id="Progress"> + <source>Progress</source> + <target>undefined</target> + </trans-unit> + <trans-unit id="Progress Bar"> + <source>Progress Bar</source> + <target>undefined</target> + </trans-unit> + <trans-unit id="progress bar timing: currentTime={1} duration={2}"> + <source>{1} of {2}</source> + <target>undefined</target> + </trans-unit> + <trans-unit id="Fullscreen"> + <source>Fullscreen</source> + <target>undefined</target> + </trans-unit> + <trans-unit id="Non-Fullscreen"> + <source>Non-Fullscreen</source> + <target>undefined</target> + </trans-unit> + <trans-unit id="Mute"> + <source>Mute</source> + <target>undefined</target> + </trans-unit> + <trans-unit id="Unmute"> + <source>Unmute</source> + <target>undefined</target> + </trans-unit> + <trans-unit id="Playback Rate"> + <source>Playback Rate</source> + <target>undefined</target> + </trans-unit> + <trans-unit id="Subtitles"> + <source>Subtitles</source> + <target>undefined</target> + </trans-unit> + <trans-unit id="subtitles off"> + <source>subtitles off</source> + <target>undefined</target> + </trans-unit> + <trans-unit id="Captions"> + <source>Captions</source> + <target>undefined</target> + </trans-unit> + <trans-unit id="captions off"> + <source>captions off</source> + <target>undefined</target> + </trans-unit> + <trans-unit id="Chapters"> + <source>Chapters</source> + <target>undefined</target> + </trans-unit> + <trans-unit id="Descriptions"> + <source>Descriptions</source> + <target>undefined</target> + </trans-unit> + <trans-unit id="descriptions off"> + <source>descriptions off</source> + <target>undefined</target> + </trans-unit> + <trans-unit id="Audio Track"> + <source>Audio Track</source> + <target>undefined</target> + </trans-unit> + <trans-unit id="Volume Level"> + <source>Volume Level</source> + <target>undefined</target> + </trans-unit> + <trans-unit id="You aborted the media playback"> + <source>You aborted the media playback</source> + <target>undefined</target> + </trans-unit> + <trans-unit id="A network error caused the media download to fail part-way."> + <source>A network error caused the media download to fail part-way.</source> + <target>undefined</target> + </trans-unit> + <trans-unit id="The media could not be loaded, either because the server or network failed or because the format is not supported."> + <source>The media could not be loaded, either because the server or network failed or because the format is not supported.</source> + <target>undefined</target> + </trans-unit> + <trans-unit id="The media playback was aborted due to a corruption problem or because the media used features your browser did not support."> + <source>The media playback was aborted due to a corruption problem or because the media used features your browser did not support.</source> + <target>undefined</target> + </trans-unit> + <trans-unit id="No compatible source was found for this media."> + <source>No compatible source was found for this media.</source> + <target>undefined</target> + </trans-unit> + <trans-unit id="The media is encrypted and we do not have the keys to decrypt it."> + <source>The media is encrypted and we do not have the keys to decrypt it.</source> + <target>undefined</target> + </trans-unit> + <trans-unit id="Play Video"> + <source>Play Video</source> + <target>undefined</target> + </trans-unit> + <trans-unit id="Close"> + <source>Close</source> + <target>undefined</target> + </trans-unit> + <trans-unit id="Close Modal Dialog"> + <source>Close Modal Dialog</source> + <target>undefined</target> + </trans-unit> + <trans-unit id="Modal Window"> + <source>Modal Window</source> + <target>undefined</target> + </trans-unit> + <trans-unit id="This is a modal window"> + <source>This is a modal window</source> + <target>undefined</target> + </trans-unit> + <trans-unit id="This modal can be closed by pressing the Escape key or activating the close button."> + <source>This modal can be closed by pressing the Escape key or activating the close button.</source> + <target>undefined</target> + </trans-unit> + <trans-unit id=", opens captions settings dialog"> + <source>, opens captions settings dialog</source> + <target>undefined</target> + </trans-unit> + <trans-unit id=", opens subtitles settings dialog"> + <source>, opens subtitles settings dialog</source> + <target>undefined</target> + </trans-unit> + <trans-unit id=", opens descriptions settings dialog"> + <source>, opens descriptions settings dialog</source> + <target>undefined</target> + </trans-unit> + <trans-unit id=", selected"> + <source>, selected</source> + <target>undefined</target> + </trans-unit> + <trans-unit id="captions settings"> + <source>captions settings</source> + <target>undefined</target> + </trans-unit> + <trans-unit id="subtitles settings"> + <source>subititles settings</source> + <target>undefined</target> + </trans-unit> + <trans-unit id="descriptions settings"> + <source>descriptions settings</source> + <target>undefined</target> + </trans-unit> + <trans-unit id="Text"> + <source>Text</source> + <target>undefined</target> + </trans-unit> + <trans-unit id="White"> + <source>White</source> + <target>undefined</target> + </trans-unit> + <trans-unit id="Black"> + <source>Black</source> + <target>undefined</target> + </trans-unit> + <trans-unit id="Red"> + <source>Red</source> + <target>undefined</target> + </trans-unit> + <trans-unit id="Green"> + <source>Green</source> + <target>undefined</target> + </trans-unit> + <trans-unit id="Blue"> + <source>Blue</source> + <target>undefined</target> + </trans-unit> + <trans-unit id="Yellow"> + <source>Yellow</source> + <target>undefined</target> + </trans-unit> + <trans-unit id="Magenta"> + <source>Magenta</source> + <target>undefined</target> + </trans-unit> + <trans-unit id="Cyan"> + <source>Cyan</source> + <target>undefined</target> + </trans-unit> + <trans-unit id="Background"> + <source>Background</source> + <target>undefined</target> + </trans-unit> + <trans-unit id="Window"> + <source>Window</source> + <target>undefined</target> + </trans-unit> + <trans-unit id="Transparent"> + <source>Transparent</source> + <target>undefined</target> + </trans-unit> + <trans-unit id="Semi-Transparent"> + <source>Semi-Transparent</source> + <target>undefined</target> + </trans-unit> + <trans-unit id="Opaque"> + <source>Opaque</source> + <target>undefined</target> + </trans-unit> + <trans-unit id="Font Size"> + <source>Font Size</source> + <target>undefined</target> + </trans-unit> + <trans-unit id="Text Edge Style"> + <source>Text Edge Style</source> + <target>undefined</target> + </trans-unit> + <trans-unit id="None"> + <source>None</source> + <target>undefined</target> + </trans-unit> + <trans-unit id="Raised"> + <source>Raised</source> + <target>undefined</target> + </trans-unit> + <trans-unit id="Depressed"> + <source>Depressed</source> + <target>undefined</target> + </trans-unit> + <trans-unit id="Uniform"> + <source>Uniform</source> + <target>undefined</target> + </trans-unit> + <trans-unit id="Dropshadow"> + <source>Dropshadow</source> + <target>undefined</target> + </trans-unit> + <trans-unit id="Font Family"> + <source>Font Family</source> + <target>undefined</target> + </trans-unit> + <trans-unit id="Proportional Sans-Serif"> + <source>Proportional Sans-Serif</source> + <target>undefined</target> + </trans-unit> + <trans-unit id="Monospace Sans-Serif"> + <source>Monospace Sans-Serif</source> + <target>undefined</target> + </trans-unit> + <trans-unit id="Proportional Serif"> + <source>Proportional Serif</source> + <target>undefined</target> + </trans-unit> + <trans-unit id="Monospace Serif"> + <source>Monospace Serif</source> + <target>undefined</target> + </trans-unit> + <trans-unit id="Casual"> + <source>Casual</source> + <target>undefined</target> + </trans-unit> + <trans-unit id="Script"> + <source>Script</source> + <target>undefined</target> + </trans-unit> + <trans-unit id="Small Caps"> + <source>Small Caps</source> + <target>undefined</target> + </trans-unit> + <trans-unit id="Reset"> + <source>Reset</source> + <target>undefined</target> + </trans-unit> + <trans-unit id="restore all settings to the default values"> + <source>restore all settings to the default values</source> + <target>undefined</target> + </trans-unit> + <trans-unit id="Done"> + <source>Done</source> + <target>undefined</target> + </trans-unit> + <trans-unit id="Caption Settings Dialog"> + <source>Caption Settings Dialog</source> + <target>undefined</target> + </trans-unit> + <trans-unit id="Beginning of dialog window. Escape will cancel and close the window."> + <source>Beginning of dialog window. Escape will cancel and close the window.</source> + <target>undefined</target> + </trans-unit> + <trans-unit id="End of dialog window."> + <source>End of dialog window.</source> + <target>undefined</target> + </trans-unit> + <trans-unit id="{1} is loading."> + <source>{1} is loading.</source> + <target>undefined</target> + </trans-unit> + <trans-unit id="Quality"> + <source>Quality</source> + <target>undefined</target> + </trans-unit> + <trans-unit id="Auto"> + <source>Auto</source> + <target>undefined</target> + </trans-unit> + <trans-unit id="Speed"> + <source>Speed</source> + <target>undefined</target> + </trans-unit> + <trans-unit id="peers"> + <source>peers</source> + <target>undefined</target> + </trans-unit> + <trans-unit id="Go to the video page"> + <source>Go to the video page</source> + <target>undefined</target> + </trans-unit> + <trans-unit id="Settings"> + <source>Settings</source> + <target>undefined</target> + </trans-unit> + <trans-unit id="Uses P2P, others may know you are watching this video."> + <source>Uses P2P, others may know you are watching this video.</source> + <target>undefined</target> + </trans-unit> + <trans-unit id="Copy the video URL"> + <source>Copy the video URL</source> + <target>undefined</target> + </trans-unit> + <trans-unit id="Copy the video URL at the current time"> + <source>Copy the video URL at the current time</source> + <target>undefined</target> + </trans-unit> + <trans-unit id="Copy embed code"> + <source>Copy embed code</source> + <target>undefined</target> + </trans-unit> + </body> + </file> +</xliff> \ No newline at end of file diff --git a/client/src/locale/source/videojs_en_US.json b/client/src/locale/source/videojs_en_US.json new file mode 100644 index 000000000..92caaa683 --- /dev/null +++ b/client/src/locale/source/videojs_en_US.json @@ -0,0 +1,85 @@ +{ + "Audio Player": "Audio Player", + "Video Player": "Video Player", + "Play": "Play", + "Pause": "Pause", + "Replay": "Replay", + "Current Time": "Current Time", + "Duration": "Duration", + "Remaining Time": "Remaining Time", + "Stream Type": "Stream Type", + "LIVE": "LIVE", + "Loaded": "Loaded", + "Progress": "Progress", + "Progress Bar": "Progress Bar", + "progress bar timing: currentTime={1} duration={2}": "{1} of {2}", + "Fullscreen": "Fullscreen", + "Non-Fullscreen": "Non-Fullscreen", + "Mute": "Mute", + "Unmute": "Unmute", + "Playback Rate": "Playback Rate", + "Subtitles": "Subtitles", + "subtitles off": "subtitles off", + "Captions": "Captions", + "captions off": "captions off", + "Chapters": "Chapters", + "Descriptions": "Descriptions", + "descriptions off": "descriptions off", + "Audio Track": "Audio Track", + "Volume Level": "Volume Level", + "You aborted the media playback": "You aborted the media playback", + "A network error caused the media download to fail part-way.": "A network error caused the media download to fail part-way.", + "The media could not be loaded, either because the server or network failed or because the format is not supported.": "The media could not be loaded, either because the server or network failed or because the format is not supported.", + "The media playback was aborted due to a corruption problem or because the media used features your browser did not support.": "The media playback was aborted due to a corruption problem or because the media used features your browser did not support.", + "No compatible source was found for this media.": "No compatible source was found for this media.", + "The media is encrypted and we do not have the keys to decrypt it.": "The media is encrypted and we do not have the keys to decrypt it.", + "Play Video": "Play Video", + "Close": "Close", + "Close Modal Dialog": "Close Modal Dialog", + "Modal Window": "Modal Window", + "This is a modal window": "This is a modal window", + "This modal can be closed by pressing the Escape key or activating the close button.": "This modal can be closed by pressing the Escape key or activating the close button.", + ", opens captions settings dialog": ", opens captions settings dialog", + ", opens subtitles settings dialog": ", opens subtitles settings dialog", + ", opens descriptions settings dialog": ", opens descriptions settings dialog", + ", selected": ", selected", + "captions settings": "captions settings", + "subtitles settings": "subititles settings", + "descriptions settings": "descriptions settings", + "Text": "Text", + "White": "White", + "Black": "Black", + "Red": "Red", + "Green": "Green", + "Blue": "Blue", + "Yellow": "Yellow", + "Magenta": "Magenta", + "Cyan": "Cyan", + "Background": "Background", + "Window": "Window", + "Transparent": "Transparent", + "Semi-Transparent": "Semi-Transparent", + "Opaque": "Opaque", + "Font Size": "Font Size", + "Text Edge Style": "Text Edge Style", + "None": "None", + "Raised": "Raised", + "Depressed": "Depressed", + "Uniform": "Uniform", + "Dropshadow": "Dropshadow", + "Font Family": "Font Family", + "Proportional Sans-Serif": "Proportional Sans-Serif", + "Monospace Sans-Serif": "Monospace Sans-Serif", + "Proportional Serif": "Proportional Serif", + "Monospace Serif": "Monospace Serif", + "Casual": "Casual", + "Script": "Script", + "Small Caps": "Small Caps", + "Reset": "Reset", + "restore all settings to the default values": "restore all settings to the default values", + "Done": "Done", + "Caption Settings Dialog": "Caption Settings Dialog", + "Beginning of dialog window. Escape will cancel and close the window.": "Beginning of dialog window. Escape will cancel and close the window.", + "End of dialog window.": "End of dialog window.", + "{1} is loading.": "{1} is loading." +} diff --git a/client/src/locale/target/player_fr.json b/client/src/locale/target/player_fr.json new file mode 100644 index 000000000..6c399fc3f --- /dev/null +++ b/client/src/locale/target/player_fr.json @@ -0,0 +1 @@ +{"Audio Player":"Lecteur audio","Video Player":"Lecteur vidéo","Play":"Lecture","Pause":"Pause","Replay":"Revoir","Current Time":"Temps actuel","Duration":"Durée","Remaining Time":"Temps restant","Stream Type":"Type de flux","LIVE":"EN DIRECT","Loaded":"Chargé","Progress":"Progression","Progress Bar":"Barre de progression","progress bar timing: currentTime={1} duration={2}":"{1} de {2}","Fullscreen":"Plein écran","Non-Fullscreen":"Fenêtré","Mute":"Sourdine","Unmute":"Son activé","Playback Rate":"Vitesse de lecture","Subtitles":"Sous-titres","subtitles off":"Sous-titres désactivés","Captions":"Sous-titres transcrits","captions off":"Sous-titres transcrits désactivés","Chapters":"Chapitres","Descriptions":"Descriptions","descriptions off":"descriptions désactivées","Audio Track":"Piste audio","Volume Level":"Niveau de volume","You aborted the media playback":"Vous avez interrompu la lecture de la vidéo.","A network error caused the media download to fail part-way.":"Une erreur de réseau a interrompu le téléchargement de la vidéo.","The media could not be loaded, either because the server or network failed or because the format is not supported.":"Cette vidéo n'a pas pu être chargée, soit parce que le serveur ou le réseau a échoué ou parce que le format n'est pas reconnu.","The media playback was aborted due to a corruption problem or because the media used features your browser did not support.":"La lecture de la vidéo a été interrompue à cause d'un problème de corruption ou parce que la vidéo utilise des fonctionnalités non prises en charge par votre navigateur.","No compatible source was found for this media.":"Aucune source compatible n'a été trouvée pour cette vidéo.","The media is encrypted and we do not have the keys to decrypt it.":"Le média est chiffré et nous n'avons pas les clés pour le déchiffrer.","Play Video":"Lire la vidéo","Close":"Fermer","Close Modal Dialog":"Fermer la boîte de dialogue modale","Modal Window":"Fenêtre modale","This is a modal window":"Ceci est une fenêtre modale","This modal can be closed by pressing the Escape key or activating the close button.":"Ce modal peut être fermé en appuyant sur la touche Échap ou activer le bouton de fermeture.",", opens captions settings dialog":", ouvrir les paramètres des sous-titres transcrits",", opens subtitles settings dialog":", ouvrir les paramètres des sous-titres",", opens descriptions settings dialog":", ouvrir les paramètres des descriptions",", selected":", sélectionné","captions settings":"Paramètres des sous-titres transcrits","subtitles settings":"Paramètres des sous-titres","descriptions settings":"Paramètres des descriptions","Text":"Texte","White":"Blanc","Black":"Noir","Red":"Rouge","Green":"Vert","Blue":"Bleu","Yellow":"Jaune","Magenta":"Magenta","Cyan":"Cyan","Background":"Arrière-plan","Window":"Fenêtre","Transparent":"Transparent","Semi-Transparent":"Semi-transparent","Opaque":"Opaque","Font Size":"Taille des caractères","Text Edge Style":"Style des contours du texte","None":"Aucun","Raised":"Élevé","Depressed":"Enfoncé","Uniform":"Uniforme","Dropshadow":"Ombre portée","Font Family":"Familles de polices","Proportional Sans-Serif":"Polices à chasse variable sans empattement (Proportional Sans-Serif)","Monospace Sans-Serif":"Polices à chasse fixe sans empattement (Monospace Sans-Serif)","Proportional Serif":"Polices à chasse variable avec empattement (Proportional Serif)","Monospace Serif":"Polices à chasse fixe avec empattement (Monospace Serif)","Casual":"Manuscrite","Script":"Scripte","Small Caps":"Petites capitales","Reset":"Réinitialiser","restore all settings to the default values":"Restaurer tous les paramètres aux valeurs par défaut","Done":"Terminé","Caption Settings Dialog":"Boîte de dialogue des paramètres des sous-titres transcrits","Beginning of dialog window. Escape will cancel and close the window.":"Début de la fenêtre de dialogue. La touche d'échappement annulera et fermera la fenêtre.","End of dialog window.":"Fin de la fenêtre de dialogue.","{1} is loading.":"{1} est en train de charger","Quality":"Qualité","Auto":"Auto","Speed":"Vitesse","peers":"pairs","Go to the video page":"Aller sur la page de la vidéo","Settings":"Paramètres","Uses P2P, others may know you are watching this video.":"Utilise le P2P, d'autres personnes pourraient savoir que vous regardez cette vidéo.","Copy the video URL":"Copier le lien de la vidéo","Copy the video URL at the current time":"Copier le lien de la vidéo à partir de cette séquence","Copy embed code":"Copier le code d'intégration"} \ No newline at end of file diff --git a/client/src/locale/target/player_fr.xml b/client/src/locale/target/player_fr.xml new file mode 100644 index 000000000..eafa4baff --- /dev/null +++ b/client/src/locale/target/player_fr.xml @@ -0,0 +1,379 @@ +<?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="Audio Player"> + <source>Audio Player</source> + <target>Lecteur audio</target> + </trans-unit> + <trans-unit id="Video Player"> + <source>Video Player</source> + <target>Lecteur vidéo</target> + </trans-unit> + <trans-unit id="Play"> + <source>Play</source> + <target>Lecture</target> + </trans-unit> + <trans-unit id="Pause"> + <source>Pause</source> + <target>Pause</target> + </trans-unit> + <trans-unit id="Replay"> + <source>Replay</source> + <target>Revoir</target> + </trans-unit> + <trans-unit id="Current Time"> + <source>Current Time</source> + <target>Temps actuel</target> + </trans-unit> + <trans-unit id="Duration"> + <source>Duration</source> + <target>Durée</target> + </trans-unit> + <trans-unit id="Remaining Time"> + <source>Remaining Time</source> + <target>Temps restant</target> + </trans-unit> + <trans-unit id="Stream Type"> + <source>Stream Type</source> + <target>Type de flux</target> + </trans-unit> + <trans-unit id="LIVE"> + <source>LIVE</source> + <target>EN DIRECT</target> + </trans-unit> + <trans-unit id="Loaded"> + <source>Loaded</source> + <target>Chargé</target> + </trans-unit> + <trans-unit id="Progress"> + <source>Progress</source> + <target>Progression</target> + </trans-unit> + <trans-unit id="Progress Bar"> + <source>Progress Bar</source> + <target>Barre de progression</target> + </trans-unit> + <trans-unit id="progress bar timing: currentTime={1} duration={2}"> + <source>{1} of {2}</source> + <target>{1} de {2}</target> + </trans-unit> + <trans-unit id="Fullscreen"> + <source>Fullscreen</source> + <target>Plein écran</target> + </trans-unit> + <trans-unit id="Non-Fullscreen"> + <source>Non-Fullscreen</source> + <target>Fenêtré</target> + </trans-unit> + <trans-unit id="Mute"> + <source>Mute</source> + <target>Sourdine</target> + </trans-unit> + <trans-unit id="Unmute"> + <source>Unmute</source> + <target>Son activé</target> + </trans-unit> + <trans-unit id="Playback Rate"> + <source>Playback Rate</source> + <target>Vitesse de lecture</target> + </trans-unit> + <trans-unit id="Subtitles"> + <source>Subtitles</source> + <target>Sous-titres</target> + </trans-unit> + <trans-unit id="subtitles off"> + <source>subtitles off</source> + <target>Sous-titres désactivés</target> + </trans-unit> + <trans-unit id="Captions"> + <source>Captions</source> + <target>Sous-titres transcrits</target> + </trans-unit> + <trans-unit id="captions off"> + <source>captions off</source> + <target>Sous-titres transcrits désactivés</target> + </trans-unit> + <trans-unit id="Chapters"> + <source>Chapters</source> + <target>Chapitres</target> + </trans-unit> + <trans-unit id="Descriptions"> + <source>Descriptions</source> + <target>Descriptions</target> + </trans-unit> + <trans-unit id="descriptions off"> + <source>descriptions off</source> + <target>descriptions désactivées</target> + </trans-unit> + <trans-unit id="Audio Track"> + <source>Audio Track</source> + <target>Piste audio</target> + </trans-unit> + <trans-unit id="Volume Level"> + <source>Volume Level</source> + <target>Niveau de volume</target> + </trans-unit> + <trans-unit id="You aborted the media playback"> + <source>You aborted the media playback</source> + <target>Vous avez interrompu la lecture de la vidéo.</target> + </trans-unit> + <trans-unit id="A network error caused the media download to fail part-way."> + <source>A network error caused the media download to fail part-way.</source> + <target>Une erreur de réseau a interrompu le téléchargement de la vidéo.</target> + </trans-unit> + <trans-unit id="The media could not be loaded, either because the server or network failed or because the format is not supported."> + <source>The media could not be loaded, either because the server or network failed or because the format is not supported.</source> + <target>Cette vidéo n'a pas pu être chargée, soit parce que le serveur ou le réseau a échoué ou parce que le format n'est pas reconnu.</target> + </trans-unit> + <trans-unit id="The media playback was aborted due to a corruption problem or because the media used features your browser did not support."> + <source>The media playback was aborted due to a corruption problem or because the media used features your browser did not support.</source> + <target>La lecture de la vidéo a été interrompue à cause d'un problème de corruption ou parce que la vidéo utilise des fonctionnalités non prises en charge par votre navigateur.</target> + </trans-unit> + <trans-unit id="No compatible source was found for this media."> + <source>No compatible source was found for this media.</source> + <target>Aucune source compatible n'a été trouvée pour cette vidéo.</target> + </trans-unit> + <trans-unit id="The media is encrypted and we do not have the keys to decrypt it."> + <source>The media is encrypted and we do not have the keys to decrypt it.</source> + <target>Le média est chiffré et nous n'avons pas les clés pour le déchiffrer.</target> + </trans-unit> + <trans-unit id="Play Video"> + <source>Play Video</source> + <target>Lire la vidéo</target> + </trans-unit> + <trans-unit id="Close"> + <source>Close</source> + <target>Fermer</target> + </trans-unit> + <trans-unit id="Close Modal Dialog"> + <source>Close Modal Dialog</source> + <target>Fermer la boîte de dialogue modale</target> + </trans-unit> + <trans-unit id="Modal Window"> + <source>Modal Window</source> + <target>Fenêtre modale</target> + </trans-unit> + <trans-unit id="This is a modal window"> + <source>This is a modal window</source> + <target>Ceci est une fenêtre modale</target> + </trans-unit> + <trans-unit id="This modal can be closed by pressing the Escape key or activating the close button."> + <source>This modal can be closed by pressing the Escape key or activating the close button.</source> + <target>Ce modal peut être fermé en appuyant sur la touche Échap ou activer le bouton de fermeture.</target> + </trans-unit> + <trans-unit id=", opens captions settings dialog"> + <source>, opens captions settings dialog</source> + <target>, ouvrir les paramètres des sous-titres transcrits</target> + </trans-unit> + <trans-unit id=", opens subtitles settings dialog"> + <source>, opens subtitles settings dialog</source> + <target>, ouvrir les paramètres des sous-titres</target> + </trans-unit> + <trans-unit id=", opens descriptions settings dialog"> + <source>, opens descriptions settings dialog</source> + <target>, ouvrir les paramètres des descriptions</target> + </trans-unit> + <trans-unit id=", selected"> + <source>, selected</source> + <target>, sélectionné</target> + </trans-unit> + <trans-unit id="captions settings"> + <source>captions settings</source> + <target>Paramètres des sous-titres transcrits</target> + </trans-unit> + <trans-unit id="subtitles settings"> + <source>subititles settings</source> + <target>Paramètres des sous-titres</target> + </trans-unit> + <trans-unit id="descriptions settings"> + <source>descriptions settings</source> + <target>Paramètres des descriptions</target> + </trans-unit> + <trans-unit id="Text"> + <source>Text</source> + <target>Texte</target> + </trans-unit> + <trans-unit id="White"> + <source>White</source> + <target>Blanc</target> + </trans-unit> + <trans-unit id="Black"> + <source>Black</source> + <target>Noir</target> + </trans-unit> + <trans-unit id="Red"> + <source>Red</source> + <target>Rouge</target> + </trans-unit> + <trans-unit id="Green"> + <source>Green</source> + <target>Vert</target> + </trans-unit> + <trans-unit id="Blue"> + <source>Blue</source> + <target>Bleu</target> + </trans-unit> + <trans-unit id="Yellow"> + <source>Yellow</source> + <target>Jaune</target> + </trans-unit> + <trans-unit id="Magenta"> + <source>Magenta</source> + <target>Magenta</target> + </trans-unit> + <trans-unit id="Cyan"> + <source>Cyan</source> + <target>Cyan</target> + </trans-unit> + <trans-unit id="Background"> + <source>Background</source> + <target>Arrière-plan</target> + </trans-unit> + <trans-unit id="Window"> + <source>Window</source> + <target>Fenêtre</target> + </trans-unit> + <trans-unit id="Transparent"> + <source>Transparent</source> + <target>Transparent</target> + </trans-unit> + <trans-unit id="Semi-Transparent"> + <source>Semi-Transparent</source> + <target>Semi-transparent</target> + </trans-unit> + <trans-unit id="Opaque"> + <source>Opaque</source> + <target>Opaque</target> + </trans-unit> + <trans-unit id="Font Size"> + <source>Font Size</source> + <target>Taille des caractères</target> + </trans-unit> + <trans-unit id="Text Edge Style"> + <source>Text Edge Style</source> + <target>Style des contours du texte</target> + </trans-unit> + <trans-unit id="None"> + <source>None</source> + <target>Aucun</target> + </trans-unit> + <trans-unit id="Raised"> + <source>Raised</source> + <target>Élevé</target> + </trans-unit> + <trans-unit id="Depressed"> + <source>Depressed</source> + <target>Enfoncé</target> + </trans-unit> + <trans-unit id="Uniform"> + <source>Uniform</source> + <target>Uniforme</target> + </trans-unit> + <trans-unit id="Dropshadow"> + <source>Dropshadow</source> + <target>Ombre portée</target> + </trans-unit> + <trans-unit id="Font Family"> + <source>Font Family</source> + <target>Familles de polices</target> + </trans-unit> + <trans-unit id="Proportional Sans-Serif"> + <source>Proportional Sans-Serif</source> + <target>Polices à chasse variable sans empattement (Proportional Sans-Serif)</target> + </trans-unit> + <trans-unit id="Monospace Sans-Serif"> + <source>Monospace Sans-Serif</source> + <target>Polices à chasse fixe sans empattement (Monospace Sans-Serif)</target> + </trans-unit> + <trans-unit id="Proportional Serif"> + <source>Proportional Serif</source> + <target>Polices à chasse variable avec empattement (Proportional Serif)</target> + </trans-unit> + <trans-unit id="Monospace Serif"> + <source>Monospace Serif</source> + <target>Polices à chasse fixe avec empattement (Monospace Serif)</target> + </trans-unit> + <trans-unit id="Casual"> + <source>Casual</source> + <target>Manuscrite</target> + </trans-unit> + <trans-unit id="Script"> + <source>Script</source> + <target>Scripte</target> + </trans-unit> + <trans-unit id="Small Caps"> + <source>Small Caps</source> + <target>Petites capitales</target> + </trans-unit> + <trans-unit id="Reset"> + <source>Reset</source> + <target>Réinitialiser</target> + </trans-unit> + <trans-unit id="restore all settings to the default values"> + <source>restore all settings to the default values</source> + <target>Restaurer tous les paramètres aux valeurs par défaut</target> + </trans-unit> + <trans-unit id="Done"> + <source>Done</source> + <target>Terminé</target> + </trans-unit> + <trans-unit id="Caption Settings Dialog"> + <source>Caption Settings Dialog</source> + <target>Boîte de dialogue des paramètres des sous-titres transcrits</target> + </trans-unit> + <trans-unit id="Beginning of dialog window. Escape will cancel and close the window."> + <source>Beginning of dialog window. Escape will cancel and close the window.</source> + <target>Début de la fenêtre de dialogue. La touche d'échappement annulera et fermera la fenêtre.</target> + </trans-unit> + <trans-unit id="End of dialog window."> + <source>End of dialog window.</source> + <target>Fin de la fenêtre de dialogue.</target> + </trans-unit> + <trans-unit id="{1} is loading."> + <source>{1} is loading.</source> + <target>{1} est en train de charger</target> + </trans-unit> + <trans-unit id="Quality"> + <source>Quality</source> + <target>Qualité</target> + </trans-unit> + <trans-unit id="Auto"> + <source>Auto</source> + <target>Auto</target> + </trans-unit> + <trans-unit id="Speed"> + <source>Speed</source> + <target>Vitesse</target> + </trans-unit> + <trans-unit id="peers"> + <source>peers</source> + <target>pairs</target> + </trans-unit> + <trans-unit id="Go to the video page"> + <source>Go to the video page</source> + <target>Aller sur la page de la vidéo</target> + </trans-unit> + <trans-unit id="Settings"> + <source>Settings</source> + <target>Paramètres</target> + </trans-unit> + <trans-unit id="Uses P2P, others may know you are watching this video."> + <source>Uses P2P, others may know you are watching this video.</source> + <target>Utilise le P2P, d'autres personnes pourraient savoir que vous regardez cette vidéo.</target> + </trans-unit> + <trans-unit id="Copy the video URL"> + <source>Copy the video URL</source> + <target>Copier le lien de la vidéo</target> + </trans-unit> + <trans-unit id="Copy the video URL at the current time"> + <source>Copy the video URL at the current time</source> + <target>Copier le lien de la vidéo à partir de cette séquence</target> + </trans-unit> + <trans-unit id="Copy embed code"> + <source>Copy embed code</source> + <target>Copier le code d'intégration</target> + </trans-unit> + </body> + </file></xliff> \ No newline at end of file diff --git a/client/src/standalone/videos/embed.ts b/client/src/standalone/videos/embed.ts index d603690ca..166013226 100644 --- a/client/src/standalone/videos/embed.ts +++ b/client/src/standalone/videos/embed.ts @@ -14,14 +14,14 @@ import 'core-js/es6/regexp' import 'core-js/es6/map' import 'core-js/es6/weak-map' import 'core-js/es6/set' - // For google bot that uses Chrome 41 and does not understand fetch import 'whatwg-fetch' import * as videojs from 'video.js' import { VideoDetails } from '../../../../shared' -import { getVideojsOptions } from '../../assets/player/peertube-player' +import { addContextMenu, getVideojsOptions, loadLocale } from '../../assets/player/peertube-player' +import { environment } from '../../environments/environment' function getVideoUrl (id: string) { return window.location.origin + '/api/v1/videos/' + id @@ -61,7 +61,8 @@ function videoFetchError (videoElement: HTMLVideoElement) { const urlParts = window.location.href.split('/') const videoId = urlParts[urlParts.length - 1] -loadVideoInfo(videoId) +loadLocale(environment.apiUrl, videojs, navigator.language) + .then(() => loadVideoInfo(videoId)) .then(async response => { const videoContainerId = 'video-container' const videoElement = document.getElementById(videoContainerId) as HTMLVideoElement @@ -91,7 +92,6 @@ loadVideoInfo(videoId) const videojsOptions = getVideojsOptions({ autoplay, inactivityTimeout: 1500, - videoEmbedUrl: window.location.origin + videoInfo.embedPath, videoViewUrl: getVideoUrl(videoId) + '/views', playerElement: videoElement, videoFiles: videoInfo.files, @@ -106,8 +106,10 @@ loadVideoInfo(videoId) player.dock({ title: videoInfo.name, - description: 'Uses P2P, others may know you are watching this video.' + description: player.localize('Uses P2P, others may know you are watching this video.') }) + + addContextMenu(player, window.location.origin + videoInfo.embedPath) }) }) .catch(err => console.error(err)) diff --git a/package.json b/package.json index 21701e664..707579af3 100644 --- a/package.json +++ b/package.json @@ -30,6 +30,8 @@ "danger:clean:prod": "scripty", "danger:clean:modules": "scripty", "i18n:generate": "scripty", + "i18n:xliff2json": "node ./dist/scripts/i18n/xliff2json.js", + "i18n:create-custom-files": "node ./dist/scripts/i18n/create-custom-files.js", "reset-password": "node ./dist/scripts/reset-password.js", "play": "scripty", "dev": "scripty", @@ -174,6 +176,7 @@ "tslint-config-standard": "^7.0.0", "typescript": "^2.5.2", "webtorrent": "^0.100.0", + "xliff": "^3.0.1", "youtube-dl": "^1.12.2" }, "scripty": { diff --git a/scripts/i18n/create-custom-files.ts b/scripts/i18n/create-custom-files.ts new file mode 100755 index 000000000..3895b3b9d --- /dev/null +++ b/scripts/i18n/create-custom-files.ts @@ -0,0 +1,49 @@ +import * as jsToXliff12 from 'xliff/jsToXliff12' +import { writeFile } from 'fs' +import { join } from 'path' + +// First, the player +const playerSource = join(__dirname, '../../../client/src/locale/source/videojs_en_US.json') +const playerTarget = join(__dirname, '../../../client/src/locale/source/player_en_US.xml') + +const videojs = require(playerSource) +const playerKeys = { + 'Quality': 'Quality', + 'Auto': 'Auto', + 'Speed': 'Speed', + 'peers': 'peers', + 'Go to the video page': 'Go to the video page', + 'Settings': 'Settings', + 'Uses P2P, others may know you are watching this video.': 'Uses P2P, others may know you are watching this video.', + 'Copy the video URL': 'Copy the video URL', + 'Copy the video URL at the current time': 'Copy the video URL at the current time', + 'Copy embed code': 'Copy embed code' +} + +const obj = { + resources: { + namespace1: {} + } +} + +for (const sourceObject of [ videojs, playerKeys ]) { + Object.keys(sourceObject).forEach(k => obj.resources.namespace1[ k ] = { source: sourceObject[ k ] }) +} + +jsToXliff12(obj, (err, res) => { + if (err) { + console.error(err) + process.exit(-1) + } + + writeFile(playerTarget, res, err => { + if (err) { + console.error(err) + process.exit(-1) + } + + process.exit(0) + }) +}) + +// Then, the server strings diff --git a/scripts/i18n/generate.sh b/scripts/i18n/generate.sh index f8ad8a3c1..0a5b6dee1 100755 --- a/scripts/i18n/generate.sh +++ b/scripts/i18n/generate.sh @@ -9,4 +9,8 @@ npm run ngx-extractor -- --locale "en-US" -i 'src/**/*.ts' -f xlf -o src/locale/ # Zanata does not support inner elements in <source>, so we hack these special elements # This regex translate the Angular elements to special entities (that we will reconvert on pull) #sed -i 's/<x id=\(.\+\?\)\/>/\<x id=\1\/\>/g' src/locale/source/messages_en_US.xml -perl -pi -e 's|<x id=(.+?)/>|<x id=\1/>|g' src/locale/source/messages_en_US.xml \ No newline at end of file +perl -pi -e 's|<x id=(.+?)/>|<x id=\1/>|g' src/locale/source/messages_en_US.xml + +# Add our strings too +cd ../ +npm run i18n:create-custom-files \ No newline at end of file diff --git a/scripts/i18n/pull-hook.sh b/scripts/i18n/pull-hook.sh index bbe4a813e..dec426b88 100755 --- a/scripts/i18n/pull-hook.sh +++ b/scripts/i18n/pull-hook.sh @@ -7,5 +7,7 @@ set -eu #sed -i 's/\<x id=\(.\+\?\)\/\>/<x id=\1\/>/g' client/src/locale/target/* for i in 1 2 3; do - perl -pi -e 's|<x id=(.+?)/>([^"])|<x id=\1/>\2|g' client/src/locale/target/* -done \ No newline at end of file + perl -pi -e 's|<x id=(.+?)/>([^"])|<x id=\1/>\2|g' client/src/locale/target/*.xml +done + +npm run i18n:xliff2json \ No newline at end of file diff --git a/scripts/i18n/xliff2json.ts b/scripts/i18n/xliff2json.ts new file mode 100755 index 000000000..34784ac11 --- /dev/null +++ b/scripts/i18n/xliff2json.ts @@ -0,0 +1,42 @@ +import * as xliff12ToJs from 'xliff/xliff12ToJs' +import { readFileSync, writeFile } from 'fs' +import { join } from 'path' + +// First, the player +const playerSource = join(__dirname, '../../../client/src/locale/target/player_fr.xml') +const playerTarget = join(__dirname, '../../../client/src/locale/target/player_fr.json') + +// Remove the two first lines our xliff module does not like +let playerFile = readFileSync(playerSource).toString() +playerFile = removeFirstLine(playerFile) +playerFile = removeFirstLine(playerFile) + +xliff12ToJs(playerFile, (err, res) => { + if (err) { + console.error(err) + process.exit(-1) + } + + const json = createJSONString(res) + writeFile(playerTarget, json, err => { + if (err) { + console.error(err) + process.exit(-1) + } + + process.exit(0) + }) +}) + +function removeFirstLine (str: string) { + return str.substring(str.indexOf('\n') + 1) +} + +function createJSONString (obj: any) { + const res: any = {} + const strings = obj.resources[''] + + Object.keys(strings).forEach(k => res[k] = strings[k].target) + + return JSON.stringify(res) +} diff --git a/scripts/watch/server.sh b/scripts/watch/server.sh index 6250fb9a4..badbf3da0 100755 --- a/scripts/watch/server.sh +++ b/scripts/watch/server.sh @@ -2,6 +2,11 @@ set -eu +# Copy locales +mkdir -p "./client/dist" +rm -r "./client/dist/locale" +cp -r "./client/src/locale/target" "./client/dist/locale" + NODE_ENV=test concurrently -k \ "npm run tsc -- --sourceMap && npm run nodemon -- --delay 2 --watch ./dist dist/server" \ "npm run tsc -- --sourceMap --preserveWatchOutput -w" diff --git a/server/controllers/client.ts b/server/controllers/client.ts index 4b37b5fa6..b153f6086 100644 --- a/server/controllers/client.ts +++ b/server/controllers/client.ts @@ -47,6 +47,14 @@ for (const staticClientFile of staticClientFiles) { clientsRouter.use('/client', express.static(distPath, { maxAge: STATIC_MAX_AGE })) clientsRouter.use('/client/assets/images', express.static(assetsImagesPath, { maxAge: STATIC_MAX_AGE })) +clientsRouter.use('/client/locales/:locale/:file.json', function (req, res) { + if (req.params.locale === 'fr' && req.params.file === 'player') { + return res.sendFile(join(__dirname, '../../../client/dist/locale/player_fr.json')) + } + + return res.sendStatus(404) +}) + // 404 for static files not found clientsRouter.use('/client/*', (req: express.Request, res: express.Response, next: express.NextFunction) => { res.sendStatus(404) diff --git a/shared/models/i18n/i18n.ts b/shared/models/i18n/i18n.ts index 2d3a1d3e2..4d50bc36e 100644 --- a/shared/models/i18n/i18n.ts +++ b/shared/models/i18n/i18n.ts @@ -7,6 +7,10 @@ export function getDefaultLocale () { return 'en-US' } +export function isDefaultLocale (locale: string) { + return locale === getDefaultLocale() +} + const possiblePaths = Object.keys(I18N_LOCALES).map(l => '/' + l) export function is18nPath (path: string) { return possiblePaths.indexOf(path) !== -1 diff --git a/yarn.lock b/yarn.lock index eb06faac0..3b6c7574e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8402,6 +8402,18 @@ xhr2@^0.1.4: version "0.1.4" resolved "https://registry.yarnpkg.com/xhr2/-/xhr2-0.1.4.tgz#7f87658847716db5026323812f818cadab387a5f" +xliff@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/xliff/-/xliff-3.0.1.tgz#ea0f5840011727aecbddf111e5c26d8590dcca9b" + dependencies: + xml-js "1.6.2" + +xml-js@1.6.2: + version "1.6.2" + resolved "https://registry.yarnpkg.com/xml-js/-/xml-js-1.6.2.tgz#4c4cb8413998f73701a202a1b8b2f17c985a72c5" + dependencies: + sax "^1.2.4" + xml-name-validator@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/xml-name-validator/-/xml-name-validator-3.0.0.tgz#6ae73e06de4d8c6e47f9fb181f78d648ad457c6a" -- cgit v1.2.3