]> git.immae.eu Git - github/Chocobozzz/PeerTube.git/blobdiff - server/lib/plugins/plugin-manager.ts
Update validator dependency
[github/Chocobozzz/PeerTube.git] / server / lib / plugins / plugin-manager.ts
index 81554a09e7b1156f6383766273d9b516ffb31a48..8127992b59315d45a727a9608e26b07c525b2437 100644 (file)
@@ -3,7 +3,11 @@ import { logger } from '../../helpers/logger'
 import { basename, join } from 'path'
 import { CONFIG } from '../../initializers/config'
 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 { PLUGIN_GLOBAL_CSS_PATH, VIDEO_CATEGORIES, VIDEO_LANGUAGES, VIDEO_LICENCES } from '../../initializers/constants'
 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 { PluginVideoCategoryManager } from '../../../shared/models/plugins/plugin-video-category-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 {
   npmName: string
@@ -60,6 +65,10 @@ type UpdatedVideoConstant = {
   }
 }
 
+type PluginLocalesTranslations = {
+  [ locale: string ]: PluginTranslation
+}
+
 export class PluginManager implements ServerHook {
 
   private static instance: PluginManager
@@ -67,6 +76,7 @@ export class PluginManager implements ServerHook {
   private registeredPlugins: { [ name: string ]: RegisteredPlugin } = {}
   private settings: { [ name: string ]: RegisterServerSettingOptions[] } = {}
   private hooks: { [ name: string ]: HookInformationValue[] } = {}
+  private translations: PluginLocalesTranslations = {}
 
   private updatedVideoConstants: UpdatedVideoConstant = {
     language: {},
@@ -117,6 +127,10 @@ export class PluginManager implements ServerHook {
     return this.settings[npmName] || []
   }
 
+  getTranslations (locale: string) {
+    return this.translations[locale] || {}
+  }
+
   // ###################### Hooks ######################
 
   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.settings[plugin.npmName]
 
+    this.deleteTranslations(plugin.npmName)
+
     if (plugin.type === PluginType.PLUGIN) {
       await plugin.unregister()
 
@@ -206,9 +222,8 @@ export class PluginManager implements ServerHook {
       const pluginName = PluginModel.normalizePluginName(npmName)
 
       const packageJSON = await this.getPackageJSON(pluginName, pluginType)
-      if (!isPackageJSONValid(packageJSON, pluginType)) {
-        throw new Error('PackageJSON is invalid.')
-      }
+
+      this.sanitizeAndCheckPackageJSONOrThrow(packageJSON, pluginType);
 
       [ plugin ] = await PluginModel.upsert({
         name: pluginName,
@@ -285,9 +300,7 @@ export class PluginManager implements ServerHook {
     const packageJSON = await this.getPackageJSON(plugin.name, plugin.type)
     const pluginPath = this.getPluginPath(plugin.name, plugin.type)
 
-    if (!isPackageJSONValid(packageJSON, plugin.type)) {
-      throw new Error('Package.JSON is invalid.')
-    }
+    this.sanitizeAndCheckPackageJSONOrThrow(packageJSON, plugin.type)
 
     let library: PluginLibrary
     if (plugin.type === PluginType.PLUGIN) {
@@ -312,6 +325,8 @@ export class PluginManager implements ServerHook {
       css: packageJSON.css,
       unregister: library ? library.unregister : undefined
     }
+
+    await this.addTranslations(plugin, npmName, packageJSON.translations)
   }
 
   private async registerPlugin (plugin: PluginModel, pluginPath: string, packageJSON: PluginPackageJson) {
@@ -337,6 +352,28 @@ export class PluginManager implements ServerHook {
     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 ######################
 
   private resetCSSGlobalFile () {
@@ -368,9 +405,7 @@ export class PluginManager implements ServerHook {
   private async regeneratePluginGlobalCSS () {
     await this.resetCSSGlobalFile()
 
-    for (const key of Object.keys(this.getRegisteredPlugins())) {
-      const plugin = this.registeredPlugins[key]
-
+    for (const plugin of this.getRegisteredPlugins()) {
       await this.addCSSToGlobalFile(plugin.path, plugin.css)
     }
   }
@@ -455,7 +490,7 @@ export class PluginManager implements ServerHook {
       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 }),
 
       deleteCategory: (key: number) => this.deleteConstant({ npmName, type: 'category', obj: VIDEO_CATEGORIES, key })
@@ -560,6 +595,21 @@ export class PluginManager implements ServerHook {
     }
   }
 
+  private sanitizeAndCheckPackageJSONOrThrow (packageJSON: PluginPackageJson, pluginType: PluginType) {
+    if (!packageJSON.staticDirs) packageJSON.staticDirs = {}
+    if (!packageJSON.css) packageJSON.css = []
+    if (!packageJSON.clientScripts) packageJSON.clientScripts = []
+    if (!packageJSON.translations) packageJSON.translations = {}
+
+    const { result: packageJSONValid, badFields } = isPackageJSONValid(packageJSON, pluginType)
+    if (!packageJSONValid) {
+      const formattedFields = badFields.map(f => `"${f}"`)
+                              .join(', ')
+
+      throw new Error(`PackageJSON is invalid (invalid fields: ${formattedFields}).`)
+    }
+  }
+
   static get Instance () {
     return this.instance || (this.instance = new this())
   }