mirror of https://github.com/Chocobozzz/PeerTube
				
				
				
			Add plugin translation system
							parent
							
								
									ee286591a5
								
							
						
					
					
						commit
						d75db01f14
					
				| 
						 | 
					@ -1,4 +1,4 @@
 | 
				
			||||||
import { catchError } from 'rxjs/operators'
 | 
					import { catchError, map, switchMap } from 'rxjs/operators'
 | 
				
			||||||
import { HttpClient, HttpParams } from '@angular/common/http'
 | 
					import { HttpClient, HttpParams } from '@angular/common/http'
 | 
				
			||||||
import { Injectable } from '@angular/core'
 | 
					import { Injectable } from '@angular/core'
 | 
				
			||||||
import { environment } from '../../../../environments/environment'
 | 
					import { environment } from '../../../../environments/environment'
 | 
				
			||||||
| 
						 | 
					@ -6,13 +6,14 @@ import { RestExtractor, RestService } from '../../../shared'
 | 
				
			||||||
import { I18n } from '@ngx-translate/i18n-polyfill'
 | 
					import { I18n } from '@ngx-translate/i18n-polyfill'
 | 
				
			||||||
import { PluginType } from '@shared/models/plugins/plugin.type'
 | 
					import { PluginType } from '@shared/models/plugins/plugin.type'
 | 
				
			||||||
import { ComponentPagination } from '@app/shared/rest/component-pagination.model'
 | 
					import { ComponentPagination } from '@app/shared/rest/component-pagination.model'
 | 
				
			||||||
import { ResultList } from '@shared/models'
 | 
					import { peertubeTranslate, ResultList } from '@shared/models'
 | 
				
			||||||
import { PeerTubePlugin } from '@shared/models/plugins/peertube-plugin.model'
 | 
					import { PeerTubePlugin } from '@shared/models/plugins/peertube-plugin.model'
 | 
				
			||||||
import { ManagePlugin } from '@shared/models/plugins/manage-plugin.model'
 | 
					import { ManagePlugin } from '@shared/models/plugins/manage-plugin.model'
 | 
				
			||||||
import { InstallOrUpdatePlugin } from '@shared/models/plugins/install-plugin.model'
 | 
					import { InstallOrUpdatePlugin } from '@shared/models/plugins/install-plugin.model'
 | 
				
			||||||
import { PeerTubePluginIndex } from '@shared/models/plugins/peertube-plugin-index.model'
 | 
					import { PeerTubePluginIndex } from '@shared/models/plugins/peertube-plugin-index.model'
 | 
				
			||||||
import { RegisteredServerSettings, RegisterServerSettingOptions } from '@shared/models/plugins/register-server-setting.model'
 | 
					import { RegisteredServerSettings, RegisterServerSettingOptions } from '@shared/models/plugins/register-server-setting.model'
 | 
				
			||||||
import { PluginService } from '@app/core/plugins/plugin.service'
 | 
					import { PluginService } from '@app/core/plugins/plugin.service'
 | 
				
			||||||
 | 
					import { Observable } from 'rxjs'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@Injectable()
 | 
					@Injectable()
 | 
				
			||||||
export class PluginApiService {
 | 
					export class PluginApiService {
 | 
				
			||||||
| 
						 | 
					@ -92,7 +93,10 @@ export class PluginApiService {
 | 
				
			||||||
    const path = PluginApiService.BASE_PLUGIN_URL + '/' + npmName + '/registered-settings'
 | 
					    const path = PluginApiService.BASE_PLUGIN_URL + '/' + npmName + '/registered-settings'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return this.authHttp.get<RegisteredServerSettings>(path)
 | 
					    return this.authHttp.get<RegisteredServerSettings>(path)
 | 
				
			||||||
               .pipe(catchError(res => this.restExtractor.handleError(res)))
 | 
					               .pipe(
 | 
				
			||||||
 | 
					                 switchMap(res => this.translateSettingsLabel(npmName, res)),
 | 
				
			||||||
 | 
					                 catchError(res => this.restExtractor.handleError(res))
 | 
				
			||||||
 | 
					               )
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  updatePluginSettings (pluginName: string, pluginType: PluginType, settings: any) {
 | 
					  updatePluginSettings (pluginName: string, pluginType: PluginType, settings: any) {
 | 
				
			||||||
| 
						 | 
					@ -129,4 +133,19 @@ export class PluginApiService {
 | 
				
			||||||
    return this.authHttp.post(PluginApiService.BASE_PLUGIN_URL + '/install', body)
 | 
					    return this.authHttp.post(PluginApiService.BASE_PLUGIN_URL + '/install', body)
 | 
				
			||||||
               .pipe(catchError(res => this.restExtractor.handleError(res)))
 | 
					               .pipe(catchError(res => this.restExtractor.handleError(res)))
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  private translateSettingsLabel (npmName: string, res: RegisteredServerSettings): Observable<RegisteredServerSettings> {
 | 
				
			||||||
 | 
					    return this.pluginService.translationsObservable
 | 
				
			||||||
 | 
					      .pipe(
 | 
				
			||||||
 | 
					        map(allTranslations => allTranslations[npmName]),
 | 
				
			||||||
 | 
					        map(translations => {
 | 
				
			||||||
 | 
					          const registeredSettings = res.registeredSettings
 | 
				
			||||||
 | 
					                                        .map(r => {
 | 
				
			||||||
 | 
					                                          return Object.assign({}, r, { label: peertubeTranslate(r.label, translations) })
 | 
				
			||||||
 | 
					                                        })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					          return { registeredSettings }
 | 
				
			||||||
 | 
					        })
 | 
				
			||||||
 | 
					      )
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,11 +1,11 @@
 | 
				
			||||||
import { Injectable, NgZone } from '@angular/core'
 | 
					import { Inject, Injectable, LOCALE_ID, NgZone } from '@angular/core'
 | 
				
			||||||
import { Router } from '@angular/router'
 | 
					import { Router } from '@angular/router'
 | 
				
			||||||
import { ServerConfigPlugin } from '@shared/models'
 | 
					import { getCompleteLocale, isDefaultLocale, peertubeTranslate, ServerConfigPlugin } from '@shared/models'
 | 
				
			||||||
import { ServerService } from '@app/core/server/server.service'
 | 
					import { ServerService } from '@app/core/server/server.service'
 | 
				
			||||||
import { ClientScript } from '@shared/models/plugins/plugin-package-json.model'
 | 
					import { ClientScript } from '@shared/models/plugins/plugin-package-json.model'
 | 
				
			||||||
import { ClientScript as ClientScriptModule } from '../../../types/client-script.model'
 | 
					import { ClientScript as ClientScriptModule } from '../../../types/client-script.model'
 | 
				
			||||||
import { environment } from '../../../environments/environment'
 | 
					import { environment } from '../../../environments/environment'
 | 
				
			||||||
import { ReplaySubject } from 'rxjs'
 | 
					import { Observable, of, ReplaySubject } from 'rxjs'
 | 
				
			||||||
import { catchError, first, map, shareReplay } from 'rxjs/operators'
 | 
					import { catchError, first, map, shareReplay } from 'rxjs/operators'
 | 
				
			||||||
import { getHookType, internalRunHook } from '@shared/core-utils/plugins/hooks'
 | 
					import { getHookType, internalRunHook } from '@shared/core-utils/plugins/hooks'
 | 
				
			||||||
import { ClientHook, ClientHookName, clientHookObject } from '@shared/models/plugins/client-hook.model'
 | 
					import { ClientHook, ClientHookName, clientHookObject } from '@shared/models/plugins/client-hook.model'
 | 
				
			||||||
| 
						 | 
					@ -15,6 +15,9 @@ import { HttpClient } from '@angular/common/http'
 | 
				
			||||||
import { RestExtractor } from '@app/shared/rest'
 | 
					import { RestExtractor } from '@app/shared/rest'
 | 
				
			||||||
import { PluginType } from '@shared/models/plugins/plugin.type'
 | 
					import { PluginType } from '@shared/models/plugins/plugin.type'
 | 
				
			||||||
import { PublicServerSetting } from '@shared/models/plugins/public-server.setting'
 | 
					import { PublicServerSetting } from '@shared/models/plugins/public-server.setting'
 | 
				
			||||||
 | 
					import { getDevLocale, isOnDevLocale } from '@app/shared/i18n/i18n-utils'
 | 
				
			||||||
 | 
					import { RegisterClientHelpers } from '../../../types/register-client-option.model'
 | 
				
			||||||
 | 
					import { PluginTranslation } from '@shared/models/plugins/plugin-translation.model'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
interface HookStructValue extends RegisterClientHookOptions {
 | 
					interface HookStructValue extends RegisterClientHookOptions {
 | 
				
			||||||
  plugin: ServerConfigPlugin
 | 
					  plugin: ServerConfigPlugin
 | 
				
			||||||
| 
						 | 
					@ -30,7 +33,8 @@ type PluginInfo = {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@Injectable()
 | 
					@Injectable()
 | 
				
			||||||
export class PluginService implements ClientHook {
 | 
					export class PluginService implements ClientHook {
 | 
				
			||||||
  private static BASE_PLUGIN_URL = environment.apiUrl + '/api/v1/plugins'
 | 
					  private static BASE_PLUGIN_API_URL = environment.apiUrl + '/api/v1/plugins'
 | 
				
			||||||
 | 
					  private static BASE_PLUGIN_URL = environment.apiUrl + '/plugins'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  pluginsBuilt = new ReplaySubject<boolean>(1)
 | 
					  pluginsBuilt = new ReplaySubject<boolean>(1)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -40,6 +44,8 @@ export class PluginService implements ClientHook {
 | 
				
			||||||
    'video-watch': new ReplaySubject<boolean>(1)
 | 
					    'video-watch': new ReplaySubject<boolean>(1)
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  translationsObservable: Observable<PluginTranslation>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  private plugins: ServerConfigPlugin[] = []
 | 
					  private plugins: ServerConfigPlugin[] = []
 | 
				
			||||||
  private scopes: { [ scopeName: string ]: PluginInfo[] } = {}
 | 
					  private scopes: { [ scopeName: string ]: PluginInfo[] } = {}
 | 
				
			||||||
  private loadedScripts: { [ script: string ]: boolean } = {}
 | 
					  private loadedScripts: { [ script: string ]: boolean } = {}
 | 
				
			||||||
| 
						 | 
					@ -53,8 +59,10 @@ export class PluginService implements ClientHook {
 | 
				
			||||||
    private server: ServerService,
 | 
					    private server: ServerService,
 | 
				
			||||||
    private zone: NgZone,
 | 
					    private zone: NgZone,
 | 
				
			||||||
    private authHttp: HttpClient,
 | 
					    private authHttp: HttpClient,
 | 
				
			||||||
    private restExtractor: RestExtractor
 | 
					    private restExtractor: RestExtractor,
 | 
				
			||||||
 | 
					    @Inject(LOCALE_ID) private localeId: string
 | 
				
			||||||
  ) {
 | 
					  ) {
 | 
				
			||||||
 | 
					    this.loadTranslations()
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  initializePlugins () {
 | 
					  initializePlugins () {
 | 
				
			||||||
| 
						 | 
					@ -235,8 +243,9 @@ export class PluginService implements ClientHook {
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  private buildPeerTubeHelpers (pluginInfo: PluginInfo) {
 | 
					  private buildPeerTubeHelpers (pluginInfo: PluginInfo): RegisterClientHelpers {
 | 
				
			||||||
    const { plugin } = pluginInfo
 | 
					    const { plugin } = pluginInfo
 | 
				
			||||||
 | 
					    const npmName = this.nameToNpmName(pluginInfo.plugin.name, pluginInfo.pluginType)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return {
 | 
					    return {
 | 
				
			||||||
      getBaseStaticRoute: () => {
 | 
					      getBaseStaticRoute: () => {
 | 
				
			||||||
| 
						 | 
					@ -245,8 +254,7 @@ export class PluginService implements ClientHook {
 | 
				
			||||||
      },
 | 
					      },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      getSettings: () => {
 | 
					      getSettings: () => {
 | 
				
			||||||
        const npmName = this.nameToNpmName(pluginInfo.plugin.name, pluginInfo.pluginType)
 | 
					        const path = PluginService.BASE_PLUGIN_API_URL + '/' + npmName + '/public-settings'
 | 
				
			||||||
        const path = PluginService.BASE_PLUGIN_URL + '/' + npmName + '/public-settings'
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return this.authHttp.get<PublicServerSetting>(path)
 | 
					        return this.authHttp.get<PublicServerSetting>(path)
 | 
				
			||||||
                   .pipe(
 | 
					                   .pipe(
 | 
				
			||||||
| 
						 | 
					@ -254,10 +262,28 @@ export class PluginService implements ClientHook {
 | 
				
			||||||
                     catchError(res => this.restExtractor.handleError(res))
 | 
					                     catchError(res => this.restExtractor.handleError(res))
 | 
				
			||||||
                   )
 | 
					                   )
 | 
				
			||||||
                   .toPromise()
 | 
					                   .toPromise()
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      translate: (value: string) => {
 | 
				
			||||||
 | 
					        return this.translationsObservable
 | 
				
			||||||
 | 
					            .pipe(map(allTranslations => allTranslations[npmName]))
 | 
				
			||||||
 | 
					            .pipe(map(translations => peertubeTranslate(value, translations)))
 | 
				
			||||||
 | 
					            .toPromise()
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  private loadTranslations () {
 | 
				
			||||||
 | 
					    const completeLocale = isOnDevLocale() ? getDevLocale() : getCompleteLocale(this.localeId)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Default locale, nothing to translate
 | 
				
			||||||
 | 
					    if (isDefaultLocale(completeLocale)) this.translationsObservable = of({}).pipe(shareReplay())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    this.translationsObservable = this.authHttp
 | 
				
			||||||
 | 
					        .get<PluginTranslation>(PluginService.BASE_PLUGIN_URL + '/translations/' + completeLocale + '.json')
 | 
				
			||||||
 | 
					        .pipe(shareReplay())
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  private getPluginPathPrefix (isTheme: boolean) {
 | 
					  private getPluginPathPrefix (isTheme: boolean) {
 | 
				
			||||||
    return isTheme ? '/themes' : '/plugins'
 | 
					    return isTheme ? '/themes' : '/plugins'
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -31,7 +31,6 @@ import { ServerService } from '@app/core'
 | 
				
			||||||
import { UserSubscriptionService } from '@app/shared/user-subscription/user-subscription.service'
 | 
					import { UserSubscriptionService } from '@app/shared/user-subscription/user-subscription.service'
 | 
				
			||||||
import { VideoChannel } from '@app/shared/video-channel/video-channel.model'
 | 
					import { VideoChannel } from '@app/shared/video-channel/video-channel.model'
 | 
				
			||||||
import { I18n } from '@ngx-translate/i18n-polyfill'
 | 
					import { I18n } from '@ngx-translate/i18n-polyfill'
 | 
				
			||||||
import { VideoPlaylist } from '@app/shared/video-playlist/video-playlist.model'
 | 
					 | 
				
			||||||
import { VideoPlaylistService } from '@app/shared/video-playlist/video-playlist.service'
 | 
					import { VideoPlaylistService } from '@app/shared/video-playlist/video-playlist.service'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export interface VideosProvider {
 | 
					export interface VideosProvider {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -3,9 +3,13 @@ import { RegisterClientHookOptions } from '@shared/models/plugins/register-clien
 | 
				
			||||||
export type RegisterClientOptions = {
 | 
					export type RegisterClientOptions = {
 | 
				
			||||||
  registerHook: (options: RegisterClientHookOptions) => void
 | 
					  registerHook: (options: RegisterClientHookOptions) => void
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  peertubeHelpers: {
 | 
					  peertubeHelpers: RegisterClientHelpers
 | 
				
			||||||
    getBaseStaticRoute: () => string
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    getSettings: () => Promise<{ [ name: string ]: string }>
 | 
					export type RegisterClientHelpers = {
 | 
				
			||||||
  }
 | 
					  getBaseStaticRoute: () => string
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  getSettings: () => Promise<{ [ name: string ]: string }>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  translate: (toTranslate: string) => Promise<string>
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,11 +1,12 @@
 | 
				
			||||||
import * as express from 'express'
 | 
					import * as express from 'express'
 | 
				
			||||||
import { PLUGIN_GLOBAL_CSS_PATH } from '../initializers/constants'
 | 
					import { PLUGIN_GLOBAL_CSS_PATH } from '../initializers/constants'
 | 
				
			||||||
import { join } from 'path'
 | 
					import { join } from 'path'
 | 
				
			||||||
import { RegisteredPlugin } from '../lib/plugins/plugin-manager'
 | 
					import { PluginManager, RegisteredPlugin } from '../lib/plugins/plugin-manager'
 | 
				
			||||||
import { servePluginStaticDirectoryValidator } from '../middlewares/validators/plugins'
 | 
					import { servePluginStaticDirectoryValidator } from '../middlewares/validators/plugins'
 | 
				
			||||||
import { serveThemeCSSValidator } from '../middlewares/validators/themes'
 | 
					import { serveThemeCSSValidator } from '../middlewares/validators/themes'
 | 
				
			||||||
import { PluginType } from '../../shared/models/plugins/plugin.type'
 | 
					import { PluginType } from '../../shared/models/plugins/plugin.type'
 | 
				
			||||||
import { isTestInstance } from '../helpers/core-utils'
 | 
					import { isTestInstance } from '../helpers/core-utils'
 | 
				
			||||||
 | 
					import { getCompleteLocale, is18nLocale } from '../../shared/models/i18n'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const sendFileOptions = {
 | 
					const sendFileOptions = {
 | 
				
			||||||
  maxAge: '30 days',
 | 
					  maxAge: '30 days',
 | 
				
			||||||
| 
						 | 
					@ -18,6 +19,10 @@ pluginsRouter.get('/plugins/global.css',
 | 
				
			||||||
  servePluginGlobalCSS
 | 
					  servePluginGlobalCSS
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pluginsRouter.get('/plugins/translations/:locale.json',
 | 
				
			||||||
 | 
					  getPluginTranslations
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
pluginsRouter.get('/plugins/:pluginName/:pluginVersion/static/:staticEndpoint(*)',
 | 
					pluginsRouter.get('/plugins/:pluginName/:pluginVersion/static/:staticEndpoint(*)',
 | 
				
			||||||
  servePluginStaticDirectoryValidator(PluginType.PLUGIN),
 | 
					  servePluginStaticDirectoryValidator(PluginType.PLUGIN),
 | 
				
			||||||
  servePluginStaticDirectory
 | 
					  servePluginStaticDirectory
 | 
				
			||||||
| 
						 | 
					@ -60,6 +65,19 @@ function servePluginGlobalCSS (req: express.Request, res: express.Response) {
 | 
				
			||||||
  return res.sendFile(PLUGIN_GLOBAL_CSS_PATH, globalCSSOptions)
 | 
					  return res.sendFile(PLUGIN_GLOBAL_CSS_PATH, globalCSSOptions)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function getPluginTranslations (req: express.Request, res: express.Response) {
 | 
				
			||||||
 | 
					  const locale = req.params.locale
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (is18nLocale(locale)) {
 | 
				
			||||||
 | 
					    const completeLocale = getCompleteLocale(locale)
 | 
				
			||||||
 | 
					    const json = PluginManager.Instance.getTranslations(completeLocale)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return res.json(json)
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return res.sendStatus(404)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function servePluginStaticDirectory (req: express.Request, res: express.Response) {
 | 
					function servePluginStaticDirectory (req: express.Request, res: express.Response) {
 | 
				
			||||||
  const plugin: RegisteredPlugin = res.locals.registeredPlugin
 | 
					  const plugin: RegisteredPlugin = res.locals.registeredPlugin
 | 
				
			||||||
  const staticEndpoint = req.params.staticEndpoint
 | 
					  const staticEndpoint = req.params.staticEndpoint
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -44,7 +44,7 @@ function isPluginHomepage (value: string) {
 | 
				
			||||||
  return isUrlValid(value)
 | 
					  return isUrlValid(value)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function isStaticDirectoriesValid (staticDirs: any) {
 | 
					function areStaticDirectoriesValid (staticDirs: any) {
 | 
				
			||||||
  if (!exists(staticDirs) || typeof staticDirs !== 'object') return false
 | 
					  if (!exists(staticDirs) || typeof staticDirs !== 'object') return false
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  for (const key of Object.keys(staticDirs)) {
 | 
					  for (const key of Object.keys(staticDirs)) {
 | 
				
			||||||
| 
						 | 
					@ -54,14 +54,24 @@ function isStaticDirectoriesValid (staticDirs: any) {
 | 
				
			||||||
  return true
 | 
					  return true
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function isClientScriptsValid (clientScripts: any[]) {
 | 
					function areClientScriptsValid (clientScripts: any[]) {
 | 
				
			||||||
  return isArray(clientScripts) &&
 | 
					  return isArray(clientScripts) &&
 | 
				
			||||||
    clientScripts.every(c => {
 | 
					    clientScripts.every(c => {
 | 
				
			||||||
      return isSafePath(c.script) && isArray(c.scopes)
 | 
					      return isSafePath(c.script) && isArray(c.scopes)
 | 
				
			||||||
    })
 | 
					    })
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function isCSSPathsValid (css: any[]) {
 | 
					function areTranslationPathsValid (translations: any) {
 | 
				
			||||||
 | 
					  if (!exists(translations) || typeof translations !== 'object') return false
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  for (const key of Object.keys(translations)) {
 | 
				
			||||||
 | 
					    if (!isSafePath(translations[key])) return false
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return true
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function areCSSPathsValid (css: any[]) {
 | 
				
			||||||
  return isArray(css) && css.every(c => isSafePath(c))
 | 
					  return isArray(css) && css.every(c => isSafePath(c))
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -77,9 +87,10 @@ function isPackageJSONValid (packageJSON: PluginPackageJson, pluginType: PluginT
 | 
				
			||||||
    exists(packageJSON.author) &&
 | 
					    exists(packageJSON.author) &&
 | 
				
			||||||
    isUrlValid(packageJSON.bugs) &&
 | 
					    isUrlValid(packageJSON.bugs) &&
 | 
				
			||||||
    (pluginType === PluginType.THEME || isSafePath(packageJSON.library)) &&
 | 
					    (pluginType === PluginType.THEME || isSafePath(packageJSON.library)) &&
 | 
				
			||||||
    isStaticDirectoriesValid(packageJSON.staticDirs) &&
 | 
					    areStaticDirectoriesValid(packageJSON.staticDirs) &&
 | 
				
			||||||
    isCSSPathsValid(packageJSON.css) &&
 | 
					    areCSSPathsValid(packageJSON.css) &&
 | 
				
			||||||
    isClientScriptsValid(packageJSON.clientScripts)
 | 
					    areClientScriptsValid(packageJSON.clientScripts) &&
 | 
				
			||||||
 | 
					    areTranslationPathsValid(packageJSON.translations)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function isLibraryCodeValid (library: any) {
 | 
					function isLibraryCodeValid (library: any) {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -3,7 +3,11 @@ import { logger } from '../../helpers/logger'
 | 
				
			||||||
import { basename, join } from 'path'
 | 
					import { basename, join } from 'path'
 | 
				
			||||||
import { CONFIG } from '../../initializers/config'
 | 
					import { CONFIG } from '../../initializers/config'
 | 
				
			||||||
import { isLibraryCodeValid, isPackageJSONValid } from '../../helpers/custom-validators/plugins'
 | 
					import { isLibraryCodeValid, isPackageJSONValid } from '../../helpers/custom-validators/plugins'
 | 
				
			||||||
import { ClientScript, PluginPackageJson } from '../../../shared/models/plugins/plugin-package-json.model'
 | 
					import {
 | 
				
			||||||
 | 
					  ClientScript,
 | 
				
			||||||
 | 
					  PluginPackageJson,
 | 
				
			||||||
 | 
					  PluginTranslationPaths as PackagePluginTranslations
 | 
				
			||||||
 | 
					} from '../../../shared/models/plugins/plugin-package-json.model'
 | 
				
			||||||
import { createReadStream, createWriteStream } from 'fs'
 | 
					import { createReadStream, createWriteStream } from 'fs'
 | 
				
			||||||
import { PLUGIN_GLOBAL_CSS_PATH, VIDEO_CATEGORIES, VIDEO_LANGUAGES, VIDEO_LICENCES } from '../../initializers/constants'
 | 
					import { PLUGIN_GLOBAL_CSS_PATH, VIDEO_CATEGORIES, VIDEO_LANGUAGES, VIDEO_LICENCES } from '../../initializers/constants'
 | 
				
			||||||
import { PluginType } from '../../../shared/models/plugins/plugin.type'
 | 
					import { PluginType } from '../../../shared/models/plugins/plugin.type'
 | 
				
			||||||
| 
						 | 
					@ -21,6 +25,7 @@ import { RegisterServerSettingOptions } from '../../../shared/models/plugins/reg
 | 
				
			||||||
import { PluginVideoLanguageManager } from '../../../shared/models/plugins/plugin-video-language-manager.model'
 | 
					import { PluginVideoLanguageManager } from '../../../shared/models/plugins/plugin-video-language-manager.model'
 | 
				
			||||||
import { PluginVideoCategoryManager } from '../../../shared/models/plugins/plugin-video-category-manager.model'
 | 
					import { PluginVideoCategoryManager } from '../../../shared/models/plugins/plugin-video-category-manager.model'
 | 
				
			||||||
import { PluginVideoLicenceManager } from '../../../shared/models/plugins/plugin-video-licence-manager.model'
 | 
					import { PluginVideoLicenceManager } from '../../../shared/models/plugins/plugin-video-licence-manager.model'
 | 
				
			||||||
 | 
					import { PluginTranslation } from '../../../shared/models/plugins/plugin-translation.model'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export interface RegisteredPlugin {
 | 
					export interface RegisteredPlugin {
 | 
				
			||||||
  npmName: string
 | 
					  npmName: string
 | 
				
			||||||
| 
						 | 
					@ -60,6 +65,10 @@ type UpdatedVideoConstant = {
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type PluginLocalesTranslations = {
 | 
				
			||||||
 | 
					  [ locale: string ]: PluginTranslation
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export class PluginManager implements ServerHook {
 | 
					export class PluginManager implements ServerHook {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  private static instance: PluginManager
 | 
					  private static instance: PluginManager
 | 
				
			||||||
| 
						 | 
					@ -67,6 +76,7 @@ export class PluginManager implements ServerHook {
 | 
				
			||||||
  private registeredPlugins: { [ name: string ]: RegisteredPlugin } = {}
 | 
					  private registeredPlugins: { [ name: string ]: RegisteredPlugin } = {}
 | 
				
			||||||
  private settings: { [ name: string ]: RegisterServerSettingOptions[] } = {}
 | 
					  private settings: { [ name: string ]: RegisterServerSettingOptions[] } = {}
 | 
				
			||||||
  private hooks: { [ name: string ]: HookInformationValue[] } = {}
 | 
					  private hooks: { [ name: string ]: HookInformationValue[] } = {}
 | 
				
			||||||
 | 
					  private translations: PluginLocalesTranslations = {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  private updatedVideoConstants: UpdatedVideoConstant = {
 | 
					  private updatedVideoConstants: UpdatedVideoConstant = {
 | 
				
			||||||
    language: {},
 | 
					    language: {},
 | 
				
			||||||
| 
						 | 
					@ -117,6 +127,10 @@ export class PluginManager implements ServerHook {
 | 
				
			||||||
    return this.settings[npmName] || []
 | 
					    return this.settings[npmName] || []
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  getTranslations (locale: string) {
 | 
				
			||||||
 | 
					    return this.translations[locale] || {}
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // ###################### Hooks ######################
 | 
					  // ###################### Hooks ######################
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  async runHook <T> (hookName: ServerHookName, result?: T, params?: any): Promise<T> {
 | 
					  async runHook <T> (hookName: ServerHookName, result?: T, params?: any): Promise<T> {
 | 
				
			||||||
| 
						 | 
					@ -173,6 +187,8 @@ export class PluginManager implements ServerHook {
 | 
				
			||||||
    delete this.registeredPlugins[plugin.npmName]
 | 
					    delete this.registeredPlugins[plugin.npmName]
 | 
				
			||||||
    delete this.settings[plugin.npmName]
 | 
					    delete this.settings[plugin.npmName]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    this.deleteTranslations(plugin.npmName)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (plugin.type === PluginType.PLUGIN) {
 | 
					    if (plugin.type === PluginType.PLUGIN) {
 | 
				
			||||||
      await plugin.unregister()
 | 
					      await plugin.unregister()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -312,6 +328,8 @@ export class PluginManager implements ServerHook {
 | 
				
			||||||
      css: packageJSON.css,
 | 
					      css: packageJSON.css,
 | 
				
			||||||
      unregister: library ? library.unregister : undefined
 | 
					      unregister: library ? library.unregister : undefined
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    await this.addTranslations(plugin, npmName, packageJSON.translations)
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  private async registerPlugin (plugin: PluginModel, pluginPath: string, packageJSON: PluginPackageJson) {
 | 
					  private async registerPlugin (plugin: PluginModel, pluginPath: string, packageJSON: PluginPackageJson) {
 | 
				
			||||||
| 
						 | 
					@ -337,6 +355,28 @@ export class PluginManager implements ServerHook {
 | 
				
			||||||
    return library
 | 
					    return library
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // ###################### Translations ######################
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  private async addTranslations (plugin: PluginModel, npmName: string, translationPaths: PackagePluginTranslations) {
 | 
				
			||||||
 | 
					    for (const locale of Object.keys(translationPaths)) {
 | 
				
			||||||
 | 
					      const path = translationPaths[locale]
 | 
				
			||||||
 | 
					      const json = await readJSON(join(this.getPluginPath(plugin.name, plugin.type), path))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      if (!this.translations[locale]) this.translations[locale] = {}
 | 
				
			||||||
 | 
					      this.translations[locale][npmName] = json
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      logger.info('Added locale %s of plugin %s.', locale, npmName)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  private deleteTranslations (npmName: string) {
 | 
				
			||||||
 | 
					    for (const locale of Object.keys(this.translations)) {
 | 
				
			||||||
 | 
					      delete this.translations[locale][npmName]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      logger.info('Deleted locale %s of plugin %s.', locale, npmName)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // ###################### CSS ######################
 | 
					  // ###################### CSS ######################
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  private resetCSSGlobalFile () {
 | 
					  private resetCSSGlobalFile () {
 | 
				
			||||||
| 
						 | 
					@ -455,7 +495,7 @@ export class PluginManager implements ServerHook {
 | 
				
			||||||
      deleteLanguage: (key: string) => this.deleteConstant({ npmName, type: 'language', obj: VIDEO_LANGUAGES, key })
 | 
					      deleteLanguage: (key: string) => this.deleteConstant({ npmName, type: 'language', obj: VIDEO_LANGUAGES, key })
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const videoCategoryManager: PluginVideoCategoryManager= {
 | 
					    const videoCategoryManager: PluginVideoCategoryManager = {
 | 
				
			||||||
      addCategory: (key: number, label: string) => this.addConstant({ npmName, type: 'category', obj: VIDEO_CATEGORIES, key, label }),
 | 
					      addCategory: (key: number, label: string) => this.addConstant({ npmName, type: 'category', obj: VIDEO_CATEGORIES, key, label }),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      deleteCategory: (key: number) => this.deleteConstant({ npmName, type: 'category', obj: VIDEO_CATEGORIES, key })
 | 
					      deleteCategory: (key: number) => this.deleteConstant({ npmName, type: 'category', obj: VIDEO_CATEGORIES, key })
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -15,5 +15,6 @@
 | 
				
			||||||
  "library": "./main.js",
 | 
					  "library": "./main.js",
 | 
				
			||||||
  "staticDirs": {},
 | 
					  "staticDirs": {},
 | 
				
			||||||
  "css": [],
 | 
					  "css": [],
 | 
				
			||||||
  "clientScripts": []
 | 
					  "clientScripts": [],
 | 
				
			||||||
 | 
					  "translations": {}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,3 @@
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					  "Hello world": "Bonjour le monde"
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,3 @@
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					  "Hello world": "Ciao, mondo!"
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -15,5 +15,9 @@
 | 
				
			||||||
  "library": "./main.js",
 | 
					  "library": "./main.js",
 | 
				
			||||||
  "staticDirs": {},
 | 
					  "staticDirs": {},
 | 
				
			||||||
  "css": [],
 | 
					  "css": [],
 | 
				
			||||||
  "clientScripts": []
 | 
					  "clientScripts": [],
 | 
				
			||||||
 | 
					  "translations": {
 | 
				
			||||||
 | 
					    "fr-FR": "./languages/fr.json",
 | 
				
			||||||
 | 
					    "it-IT": "./languages/it.json"
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,3 @@
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					  "Hi": "Coucou"
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -15,5 +15,8 @@
 | 
				
			||||||
  "library": "./main.js",
 | 
					  "library": "./main.js",
 | 
				
			||||||
  "staticDirs": {},
 | 
					  "staticDirs": {},
 | 
				
			||||||
  "css": [],
 | 
					  "css": [],
 | 
				
			||||||
  "clientScripts": []
 | 
					  "clientScripts": [],
 | 
				
			||||||
 | 
					  "translations": {
 | 
				
			||||||
 | 
					    "fr-FR": "./languages/fr.json"
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,3 +1,4 @@
 | 
				
			||||||
import './action-hooks'
 | 
					import './action-hooks'
 | 
				
			||||||
import './filter-hooks'
 | 
					import './filter-hooks'
 | 
				
			||||||
 | 
					import './translations'
 | 
				
			||||||
import './video-constants'
 | 
					import './video-constants'
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,113 @@
 | 
				
			||||||
 | 
					/* tslint:disable:no-unused-expression */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import * as chai from 'chai'
 | 
				
			||||||
 | 
					import 'mocha'
 | 
				
			||||||
 | 
					import {
 | 
				
			||||||
 | 
					  cleanupTests,
 | 
				
			||||||
 | 
					  flushAndRunMultipleServers,
 | 
				
			||||||
 | 
					  flushAndRunServer, killallServers, reRunServer,
 | 
				
			||||||
 | 
					  ServerInfo,
 | 
				
			||||||
 | 
					  waitUntilLog
 | 
				
			||||||
 | 
					} from '../../../shared/extra-utils/server/servers'
 | 
				
			||||||
 | 
					import {
 | 
				
			||||||
 | 
					  addVideoCommentReply,
 | 
				
			||||||
 | 
					  addVideoCommentThread,
 | 
				
			||||||
 | 
					  deleteVideoComment,
 | 
				
			||||||
 | 
					  getPluginTestPath,
 | 
				
			||||||
 | 
					  getVideosList,
 | 
				
			||||||
 | 
					  installPlugin,
 | 
				
			||||||
 | 
					  removeVideo,
 | 
				
			||||||
 | 
					  setAccessTokensToServers,
 | 
				
			||||||
 | 
					  updateVideo,
 | 
				
			||||||
 | 
					  uploadVideo,
 | 
				
			||||||
 | 
					  viewVideo,
 | 
				
			||||||
 | 
					  getVideosListPagination,
 | 
				
			||||||
 | 
					  getVideo,
 | 
				
			||||||
 | 
					  getVideoCommentThreads,
 | 
				
			||||||
 | 
					  getVideoThreadComments,
 | 
				
			||||||
 | 
					  getVideoWithToken,
 | 
				
			||||||
 | 
					  setDefaultVideoChannel,
 | 
				
			||||||
 | 
					  waitJobs,
 | 
				
			||||||
 | 
					  doubleFollow, getVideoLanguages, getVideoLicences, getVideoCategories, uninstallPlugin, getPluginTranslations
 | 
				
			||||||
 | 
					} from '../../../shared/extra-utils'
 | 
				
			||||||
 | 
					import { VideoCommentThreadTree } from '../../../shared/models/videos/video-comment.model'
 | 
				
			||||||
 | 
					import { VideoDetails } from '../../../shared/models/videos'
 | 
				
			||||||
 | 
					import { getYoutubeVideoUrl, importVideo } from '../../../shared/extra-utils/videos/video-imports'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const expect = chai.expect
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					describe('Test plugin translations', function () {
 | 
				
			||||||
 | 
					  let server: ServerInfo
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  before(async function () {
 | 
				
			||||||
 | 
					    this.timeout(30000)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    server = await flushAndRunServer(1)
 | 
				
			||||||
 | 
					    await setAccessTokensToServers([ server ])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    await installPlugin({
 | 
				
			||||||
 | 
					      url: server.url,
 | 
				
			||||||
 | 
					      accessToken: server.accessToken,
 | 
				
			||||||
 | 
					      path: getPluginTestPath()
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    await installPlugin({
 | 
				
			||||||
 | 
					      url: server.url,
 | 
				
			||||||
 | 
					      accessToken: server.accessToken,
 | 
				
			||||||
 | 
					      path: getPluginTestPath('-two')
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					  })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  it('Should not have translations for locale pt', async function () {
 | 
				
			||||||
 | 
					    const res = await getPluginTranslations({ url: server.url, locale: 'pt' })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    expect(res.body).to.deep.equal({})
 | 
				
			||||||
 | 
					  })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  it('Should have translations for locale fr', async function () {
 | 
				
			||||||
 | 
					    const res = await getPluginTranslations({ url: server.url, locale: 'fr-FR' })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    expect(res.body).to.deep.equal({
 | 
				
			||||||
 | 
					      'peertube-plugin-test': {
 | 
				
			||||||
 | 
					        'Hi': 'Coucou'
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      'peertube-plugin-test-two': {
 | 
				
			||||||
 | 
					        'Hello world': 'Bonjour le monde'
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					  })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  it('Should have translations of locale it', async function () {
 | 
				
			||||||
 | 
					    const res = await getPluginTranslations({ url: server.url, locale: 'it-IT' })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    expect(res.body).to.deep.equal({
 | 
				
			||||||
 | 
					      'peertube-plugin-test-two': {
 | 
				
			||||||
 | 
					        'Hello world': 'Ciao, mondo!'
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					  })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  it('Should remove the plugin and remove the locales', async function () {
 | 
				
			||||||
 | 
					    await uninstallPlugin({ url: server.url, accessToken: server.accessToken, npmName: 'peertube-plugin-test-two' })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					      const res = await getPluginTranslations({ url: server.url, locale: 'fr-FR' })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      expect(res.body).to.deep.equal({
 | 
				
			||||||
 | 
					        'peertube-plugin-test': {
 | 
				
			||||||
 | 
					          'Hi': 'Coucou'
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      })
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					      const res = await getPluginTranslations({ url: server.url, locale: 'it-IT' })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      expect(res.body).to.deep.equal({})
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  after(async function () {
 | 
				
			||||||
 | 
					    await cleanupTests([ server ])
 | 
				
			||||||
 | 
					  })
 | 
				
			||||||
 | 
					})
 | 
				
			||||||
| 
						 | 
					@ -134,6 +134,21 @@ function getPublicSettings (parameters: {
 | 
				
			||||||
  })
 | 
					  })
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function getPluginTranslations (parameters: {
 | 
				
			||||||
 | 
					  url: string,
 | 
				
			||||||
 | 
					  locale: string,
 | 
				
			||||||
 | 
					  expectedStatus?: number
 | 
				
			||||||
 | 
					}) {
 | 
				
			||||||
 | 
					  const { url, locale, expectedStatus = 200 } = parameters
 | 
				
			||||||
 | 
					  const path = '/plugins/translations/' + locale + '.json'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return makeGetRequest({
 | 
				
			||||||
 | 
					    url,
 | 
				
			||||||
 | 
					    path,
 | 
				
			||||||
 | 
					    statusCodeExpected: expectedStatus
 | 
				
			||||||
 | 
					  })
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function installPlugin (parameters: {
 | 
					function installPlugin (parameters: {
 | 
				
			||||||
  url: string,
 | 
					  url: string,
 | 
				
			||||||
  accessToken: string,
 | 
					  accessToken: string,
 | 
				
			||||||
| 
						 | 
					@ -224,6 +239,7 @@ export {
 | 
				
			||||||
  listPlugins,
 | 
					  listPlugins,
 | 
				
			||||||
  listAvailablePlugins,
 | 
					  listAvailablePlugins,
 | 
				
			||||||
  installPlugin,
 | 
					  installPlugin,
 | 
				
			||||||
 | 
					  getPluginTranslations,
 | 
				
			||||||
  getPluginsCSS,
 | 
					  getPluginsCSS,
 | 
				
			||||||
  updatePlugin,
 | 
					  updatePlugin,
 | 
				
			||||||
  getPlugin,
 | 
					  getPlugin,
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,5 +1,9 @@
 | 
				
			||||||
import { PluginClientScope } from './plugin-client-scope.type'
 | 
					import { PluginClientScope } from './plugin-client-scope.type'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export type PluginTranslationPaths = {
 | 
				
			||||||
 | 
					  [ locale: string ]: string
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export type ClientScript = {
 | 
					export type ClientScript = {
 | 
				
			||||||
  script: string,
 | 
					  script: string,
 | 
				
			||||||
  scopes: PluginClientScope[]
 | 
					  scopes: PluginClientScope[]
 | 
				
			||||||
| 
						 | 
					@ -20,4 +24,6 @@ export type PluginPackageJson = {
 | 
				
			||||||
  css: string[]
 | 
					  css: string[]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  clientScripts: ClientScript[]
 | 
					  clientScripts: ClientScript[]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  translations: PluginTranslationPaths
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,5 @@
 | 
				
			||||||
 | 
					export type PluginTranslation = {
 | 
				
			||||||
 | 
					  [ npmName: string ]: {
 | 
				
			||||||
 | 
					    [ key: string ]: string
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
		Loading…
	
		Reference in New Issue