From 04854a842e7d392fec346acd18b3cac4f5445ba1 Mon Sep 17 00:00:00 2001 From: Xavier Jouslin de Noray <xavier.jouslindenoray@savoirfairelinux.com> Date: Thu, 17 Aug 2023 11:07:41 -0400 Subject: [PATCH] PluginDescription: add translation for plugin description and language support Change-Id: Ia38ea6c8598100ef8aab6469a87a313fb587a3db --- src/controllers/plugins.controller.ts | 20 +++- src/services/file_manager.service.ts | 3 - src/services/language_manager.service.ts | 30 ++++++ src/services/plugins_manager.service.ts | 113 ++++++++++++++++++----- tests/plugins.manager.test.ts | 6 +- 5 files changed, 142 insertions(+), 30 deletions(-) create mode 100644 src/services/language_manager.service.ts diff --git a/src/controllers/plugins.controller.ts b/src/controllers/plugins.controller.ts index 262269c..1ff7c39 100644 --- a/src/controllers/plugins.controller.ts +++ b/src/controllers/plugins.controller.ts @@ -21,12 +21,16 @@ import {type Request, type Response, Router} from 'express'; import fs from 'fs'; import {Service} from 'typedi'; import {StatusCodes} from 'http-status-codes'; +import {LanguageManager} from '../services/language_manager.service'; @Service() export class PluginsController { router = Router(); - constructor(private readonly pluginsManager: PluginsManager) { + constructor( + private readonly pluginsManager: PluginsManager, + private readonly languageManager: LanguageManager + ) { this.configureRouter(); } @@ -79,12 +83,17 @@ export class PluginsController { // eslint-disable-next-line @typescript-eslint/no-misused-promises this.router.get('/', async (req: Request, res: Response) => { try { + let langs = req.get('Accept-Language'); + if (langs === undefined) { + langs = 'en'; + } + const lang = this.languageManager.getLanguage(langs); const arch: string = req.query.arch as string; if (arch === undefined) { res.status(StatusCodes.BAD_REQUEST).send(); return; } - const plugins = await this.pluginsManager.getPlugins(arch); + const plugins = await this.pluginsManager.getPlugins(arch, lang); res.status(StatusCodes.OK).send(plugins); } catch (e) { res.status(StatusCodes.INTERNAL_SERVER_ERROR).send(); @@ -137,13 +146,18 @@ export class PluginsController { // eslint-disable-next-line @typescript-eslint/no-misused-promises this.router.get('/details/:id', async (req: Request, res: Response) => { try { + let langs = req.get('Accept-Language'); + if (langs === undefined) { + langs = 'en'; + } + const lang = this.languageManager.getLanguage(langs); const pluginId: string = req.params.id; const arch: string = req.query.arch as string; if (pluginId === undefined || arch === undefined) { res.status(StatusCodes.BAD_REQUEST).send(); return; } - const plugin = await this.pluginsManager.getPlugin(pluginId); + const plugin = await this.pluginsManager.getPlugin(pluginId, lang); if (plugin === undefined) { res.status(StatusCodes.NOT_FOUND).send(); return; diff --git a/src/services/file_manager.service.ts b/src/services/file_manager.service.ts index 7d9583d..1317ea9 100644 --- a/src/services/file_manager.service.ts +++ b/src/services/file_manager.service.ts @@ -38,7 +38,6 @@ export class FileManagerService { } return await promises.readdir(path); } catch (e) { - console.log(e); return []; } } @@ -57,7 +56,6 @@ export class FileManagerService { zip.close(); return files.map((file: string) => file.split('/')[1]); } catch (e) { - console.log(e); return []; } } @@ -70,7 +68,6 @@ export class FileManagerService { zip.close(); return await Promise.resolve(content.toString()); } catch (e) { - console.log(e); return await Promise.resolve(''); } } diff --git a/src/services/language_manager.service.ts b/src/services/language_manager.service.ts new file mode 100644 index 0000000..171f358 --- /dev/null +++ b/src/services/language_manager.service.ts @@ -0,0 +1,30 @@ +import {Service} from 'typedi'; + +@Service() +export class LanguageManager { + getLanguage(languages: string): string { + return this.selectLanguage(this.formatLanguage(languages)); + } + + private selectLanguage( + languages: Array<{lang: string; weight: number}> + ): string { + return languages.reduce((prev, current) => { + return prev.weight > current.weight ? prev : current; + }).lang; + } + + private formatLanguage( + languages: string | undefined + ): Array<{lang: string; weight: number}> { + if (languages === undefined) { + return [{lang: 'en', weight: 1}]; + } + return languages.split(',').map(lang => { + const [langStr, weightStr] = lang.split(';'); + const weight = + weightStr !== undefined ? parseFloat(weightStr.split('=')[1]) : 1; + return {lang: langStr, weight}; + }); + } +} diff --git a/src/services/plugins_manager.service.ts b/src/services/plugins_manager.service.ts index 2ccbfe6..8d45c73 100644 --- a/src/services/plugins_manager.service.ts +++ b/src/services/plugins_manager.service.ts @@ -50,27 +50,45 @@ export class PluginsManager { } // need eslint disable because of the refactoring - async getPlugins(arch: string): Promise<Array<Omit<Plugins, 'arches'>>> { + async getPlugins( + arch: string, + lang: string + ): Promise<Array<Omit<Plugins, 'arches'>>> { if (this.plugins.length === 0) { await this.setPlugins(); } - return this.plugins - .map((plugin: Plugins) => { - return { - id: plugin.id, - name: plugin.name, - version: plugin.version, - description: plugin.description, - icon: plugin.icon, - background: plugin.background, - timestamp: plugin.timestamp, - author: plugin.author, - }; - }) - .filter(plugin => this.isPluginAvailable(plugin.id, arch)); + return await Promise.all( + this.plugins + .map(async (plugin: Plugins) => { + return { + id: plugin.id, + name: await this.formatText( + plugin.id, + plugin.arches[0], + lang, + plugin.name + ), + version: plugin.version, + description: await this.formatText( + plugin.id, + plugin.arches[0], + lang, + plugin.description + ), + icon: plugin.icon, + background: plugin.background, + timestamp: plugin.timestamp, + author: plugin.author, + }; + }) + .filter(async plugin => this.isPluginAvailable((await plugin).id, arch)) + ); } - async getPlugin(id: string): Promise< + async getPlugin( + id: string, + lang: string + ): Promise< | { id: string; name: string; @@ -92,9 +110,14 @@ export class PluginsManager { ? undefined : { id: plugin.id, - name: plugin.name, + name: await this.formatText(id, plugin.arches[0], lang, plugin.name), version: plugin.version, - description: plugin.description, + description: await this.formatText( + id, + plugin.arches[0], + lang, + plugin.description + ), icon: plugin.icon, background: plugin.background, author: plugin.author, @@ -300,7 +323,7 @@ export class PluginsManager { } const plugin = this.plugins.find( (plugin: Plugins) => - plugin.name === id && plugin.arches.includes(arch) && arch !== undefined + plugin.id === id && plugin.arches.includes(arch) && arch !== undefined ); if ( plugin === undefined || @@ -317,11 +340,11 @@ export class PluginsManager { '/../..' + process.env.DATA_DIRECTORY + '/' + - plugin.name + + plugin.id + '/' + arch + '/' + - plugin.name + + plugin.id + '.jpl', 'data/' + plugin.background ) @@ -352,4 +375,52 @@ export class PluginsManager { } } } + + private async formatText( + id: string, + arch: string, + language: string, + text: string + ): Promise<string> { + return await this.findLanguageFile(id, arch, language).then( + async (languageFile: string) => { + const pluginPath = await this.getPluginPath(id, arch); + if (pluginPath === undefined) { + return text; + } + return await this.fileManager + .readArchive(pluginPath, 'data/locale/' + languageFile) + .then((languageFileContent: string) => { + if (languageFileContent === '') { + return text; + } + const languageFileContentParsed = JSON.parse(languageFileContent); + + return text.replace( + /(\{\{([^}]+)\}\})/g, + (_: string, p1: string) => { + return languageFileContentParsed[ + p1.substring(2, p1.length - 2) + ]; + } + ); + }); + } + ); + } + + private async findLanguageFile( + id: string, + arch: string, + language: string + ): Promise<string> { + const pluginPath = await this.getPluginPath(id, arch); + if (pluginPath === undefined) { + return id + '_en'; + } + const languageFile = ( + await this.fileManager.listArchiveFiles(pluginPath, '/data/locale/') + ).find((file: string) => file.includes(language)); + return languageFile === undefined ? id + '_en.json' : languageFile; + } } diff --git a/tests/plugins.manager.test.ts b/tests/plugins.manager.test.ts index 43a427a..5ca17dc 100644 --- a/tests/plugins.manager.test.ts +++ b/tests/plugins.manager.test.ts @@ -67,7 +67,7 @@ describe('Plugins manager service tests', function () { stub(Object.getPrototypeOf(pluginsManagerService), 'isPluginAvailable').returns(true); pluginsManagerService['plugins'] = fakePlugin; - const actual = await pluginsManagerService.getPlugins('test'); + const actual = await pluginsManagerService.getPlugins('test', 'en'); expect(actual).toEqual(expectedPlugin); }); @@ -91,13 +91,13 @@ describe('Plugins manager service tests', function () { pluginsManagerService['plugins'] = expected; - const actual = await pluginsManagerService.getPlugin("test"); + const actual = await pluginsManagerService.getPlugin("test", 'en'); expect(actual).toEqual(plugin); }); it('should return undefined if plugin not found', async () => { - const actual = await pluginsManagerService.getPlugin("test"); + const actual = await pluginsManagerService.getPlugin("test", 'en'); expect(actual).toBeUndefined(); }); -- GitLab