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