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'
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
}
type AlterableVideoConstant = 'language' | 'licence' | 'category'
-type VideoConstant = { [ key in number | string ]: string }
+type VideoConstant = { [key in number | string]: string }
type UpdatedVideoConstant = {
- [ name in AlterableVideoConstant ]: {
- [ npmName: string ]: {
- added: { key: number | string, label: string }[],
+ [name in AlterableVideoConstant]: {
+ [npmName: string]: {
+ added: { key: number | string, label: string }[]
deleted: { key: number | string, label: string }[]
}
}
}
+type PluginLocalesTranslations = {
+ [locale: string]: PluginTranslation
+}
+
export class PluginManager implements ServerHook {
private static instance: PluginManager
- private registeredPlugins: { [ name: string ]: RegisteredPlugin } = {}
- private settings: { [ name: string ]: RegisterServerSettingOptions[] } = {}
- private hooks: { [ name: string ]: HookInformationValue[] } = {}
+ private registeredPlugins: { [name: string]: RegisteredPlugin } = {}
+ private settings: { [name: string]: RegisterServerSettingOptions[] } = {}
+ private hooks: { [name: string]: HookInformationValue[] } = {}
+ private translations: PluginLocalesTranslations = {}
- private updatedVideoConstants: UpdatedVideoConstant = {
+ private readonly updatedVideoConstants: UpdatedVideoConstant = {
language: {},
licence: {},
category: {}
return this.settings[npmName] || []
}
+ getTranslations (locale: string) {
+ return this.translations[locale] || {}
+ }
+
// ###################### Hooks ######################
- async runHook <T> (hookName: ServerHookName, result?: T, params?: any): Promise<T> {
+ async runHook<T> (hookName: ServerHookName, result?: T, params?: any): Promise<T> {
if (!this.hooks[hookName]) return Promise.resolve(result)
const hookType = getHookType(hookName)
delete this.registeredPlugins[plugin.npmName]
delete this.settings[plugin.npmName]
+ this.deleteTranslations(plugin.npmName)
+
if (plugin.type === PluginType.PLUGIN) {
await plugin.unregister()
// Remove hooks of this plugin
for (const key of Object.keys(this.hooks)) {
- this.hooks[key] = this.hooks[key].filter(h => h.pluginName !== npmName)
+ this.hooks[key] = this.hooks[key].filter(h => h.npmName !== npmName)
}
this.reinitVideoConstants(plugin.npmName)
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,
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) {
clientScripts[c.script] = c
}
- this.registeredPlugins[ npmName ] = {
+ this.registeredPlugins[npmName] = {
npmName,
name: plugin.name,
type: plugin.type,
css: packageJSON.css,
unregister: library ? library.unregister : undefined
}
+
+ await this.addTranslations(plugin, npmName, packageJSON.translations)
}
private async registerPlugin (plugin: PluginModel, pluginPath: string, packageJSON: PluginPackageJson) {
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 () {
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)
}
}
const plugins: RegisteredPlugin[] = []
for (const npmName of Object.keys(this.registeredPlugins)) {
- const plugin = this.registeredPlugins[ npmName ]
+ const plugin = this.registeredPlugins[npmName]
if (plugin.type !== type) continue
plugins.push(plugin)
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 })
}
}
- private addConstant <T extends string | number> (parameters: {
- npmName: string,
- type: AlterableVideoConstant,
- obj: VideoConstant,
- key: T,
+ private addConstant<T extends string | number> (parameters: {
+ npmName: string
+ type: AlterableVideoConstant
+ obj: VideoConstant
+ key: T
label: string
}) {
const { npmName, type, obj, key, label } = parameters
return true
}
- private deleteConstant <T extends string | number> (parameters: {
- npmName: string,
- type: AlterableVideoConstant,
- obj: VideoConstant,
+ private deleteConstant<T extends string | number> (parameters: {
+ npmName: string
+ type: AlterableVideoConstant
+ obj: VideoConstant
key: T
}) {
const { npmName, type, obj, key } = parameters
}
}
+ 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())
}