From bb9ea277069afaa05fc5001e147c922a67e57184 Mon Sep 17 00:00:00 2001 From: Steph Beneschan <36548653+steph-beneschan-256@users.noreply.github.com> Date: Mon, 4 Mar 2024 00:11:56 -0800 Subject: [PATCH 1/8] Modify objectBrowser.ts --- src/views/objectBrowser.ts | 47 +++++++++++++++++++++++++++++++++++--- 1 file changed, 44 insertions(+), 3 deletions(-) diff --git a/src/views/objectBrowser.ts b/src/views/objectBrowser.ts index 797fbdfac..fdb7bd9ab 100644 --- a/src/views/objectBrowser.ts +++ b/src/views/objectBrowser.ts @@ -1,7 +1,7 @@ import fs from "fs"; import os from "os"; import util from "util"; -import vscode from "vscode"; +import vscode, { Uri } from "vscode"; import { ConnectionConfiguration, DefaultOpenMode, GlobalConfiguration } from "../api/Configuration"; import { parseFilter } from "../api/Filter"; import { MemberParts } from "../api/IBMi"; @@ -9,7 +9,7 @@ import { SortOptions, SortOrder } from "../api/IBMiContent"; import { Search } from "../api/Search"; import { GlobalStorage } from '../api/Storage'; import { Tools } from "../api/Tools"; -import { getMemberUri } from "../filesystems/qsys/QSysFs"; +import { getMemberUri, getUriFromPath } from "../filesystems/qsys/QSysFs"; import { instance, setSearchResults } from "../instantiate"; import { t } from "../locale"; import { BrowserItem, BrowserItemParameters, CommandResult, FilteredItem, FocusOptions, IBMiMember, IBMiObject, MemberItem, ObjectItem, SourcePhysicalFileItem } from "../typings"; @@ -320,6 +320,24 @@ class ObjectBrowserMemberItem extends ObjectBrowserItem implements MemberItem { } } +// For a given member's path or uri, find an open tab (if any) +// where that member is being edited. +// Assume that the member is not open in more than one tab. +function findMemberTab(member: vscode.Uri | string): vscode.Tab | undefined { + let memberTab: vscode.Tab | undefined; + const memberPath = ((member instanceof Uri) ? member.path : member).toLowerCase(); + for (const group of vscode.window.tabGroups.all) { + memberTab = group.tabs.find(tab => + (tab.input instanceof vscode.TabInputText) + && (tab.input.uri.scheme === `member`) + && (tab.input.uri.path.toLowerCase() === memberPath) + ); + if(memberTab) + break; + } + return memberTab; +} + export function initializeObjectBrowser(context: vscode.ExtensionContext) { const objectBrowser = new ObjectBrowser(); const objectTreeViewer = vscode.window.createTreeView( @@ -633,12 +651,25 @@ export function initializeObjectBrowser(context: vscode.ExtensionContext) { vscode.commands.registerCommand(`code-for-ibmi.renameMember`, async (node: ObjectBrowserMemberItem) => { const connection = getConnection(); const oldMember = connection.parserMemberPath(node.path); + const oldUri = node.resourceUri as vscode.Uri; const library = oldMember.library; const sourceFile = oldMember.file; let newBasename: string | undefined = oldMember.basename; let newMember: MemberParts | undefined; + let newMemberPath: string | undefined; let newNameOK; + // Check if the member is currently open in an editor tab. + const oldMemberTab = findMemberTab(oldUri); + // If the member is currently open in an editor tab, and + // the member has unsaved changes, then prevent the renaming. + // (This is because of uhhh something) + // If the member is currently being edited, forbid the rename operation. + if(oldMemberTab && oldMemberTab.isDirty) { + vscode.window.showErrorMessage(t("objectBrowser.renameMember.errorMessage", "The member has unsaved changes.")); + return; + } + do { newBasename = await vscode.window.showInputBox({ value: newBasename, @@ -648,8 +679,9 @@ export function initializeObjectBrowser(context: vscode.ExtensionContext) { if (newBasename) { newNameOK = true; + newMemberPath = library + `/` + sourceFile + `/` + newBasename; try { - newMember = connection.parserMemberPath(library + `/` + sourceFile + `/` + newBasename); + newMember = connection.parserMemberPath(newMemberPath); } catch (e: any) { newNameOK = false; vscode.window.showErrorMessage(e); @@ -685,6 +717,15 @@ export function initializeObjectBrowser(context: vscode.ExtensionContext) { } } } while (newBasename && !newNameOK) + + // If the member was open in an editor tab, refresh that tab. + // (Evidently the VS Code API does not allow us to modify an + // open tab to change its label or its uri, so we have to close + // the existing tab and then open a new one at the updated uri. + if (oldMemberTab && newNameOK && newMemberPath) { + vscode.window.tabGroups.close(oldMemberTab); + vscode.commands.executeCommand(`code-for-ibmi.openEditable`, newMemberPath); + } }), vscode.commands.registerCommand(`code-for-ibmi.uploadAndReplaceMemberAsFile`, async (node: MemberItem) => { From 728cc6f03098b94b82c5f2f964158e2b53d8440d Mon Sep 17 00:00:00 2001 From: Steph Beneschan <36548653+steph-beneschan-256@users.noreply.github.com> Date: Mon, 4 Mar 2024 05:48:48 -0800 Subject: [PATCH 2/8] Remove placeholder message --- src/views/objectBrowser.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/views/objectBrowser.ts b/src/views/objectBrowser.ts index fdb7bd9ab..079aa31ee 100644 --- a/src/views/objectBrowser.ts +++ b/src/views/objectBrowser.ts @@ -661,10 +661,9 @@ export function initializeObjectBrowser(context: vscode.ExtensionContext) { // Check if the member is currently open in an editor tab. const oldMemberTab = findMemberTab(oldUri); + // If the member is currently open in an editor tab, and - // the member has unsaved changes, then prevent the renaming. - // (This is because of uhhh something) - // If the member is currently being edited, forbid the rename operation. + // the member has unsaved changes, then prevent the renaming operation. if(oldMemberTab && oldMemberTab.isDirty) { vscode.window.showErrorMessage(t("objectBrowser.renameMember.errorMessage", "The member has unsaved changes.")); return; From 74b43e3249daaed7cecc58c72ef27fdb98fb0a34 Mon Sep 17 00:00:00 2001 From: Steph Beneschan <36548653+steph-beneschan-256@users.noreply.github.com> Date: Mon, 4 Mar 2024 19:06:34 -0800 Subject: [PATCH 3/8] Move findMemberTab function from objectBrowser.ts to Tools.ts * The findMemberTab function is now defined in Tools.js as a function of the Tools namespace --- src/api/Tools.ts | 20 ++++++++++++++++++++ src/views/objectBrowser.ts | 20 +------------------- 2 files changed, 21 insertions(+), 19 deletions(-) diff --git a/src/api/Tools.ts b/src/api/Tools.ts index 5cb450cd8..c4ad22f7f 100644 --- a/src/api/Tools.ts +++ b/src/api/Tools.ts @@ -281,6 +281,26 @@ export namespace Tools { return (isCaseSensitive ? baseUri : baseUri.toLowerCase()); } + /** + * For a given member's path or uri, find an open tab (if any) + * where that member is being edited. + * Assume that the member is not open in more than one tab. + */ + export function findMemberTab(member: vscode.Uri | string): vscode.Tab | undefined { + let memberTab: vscode.Tab | undefined; + const memberPath = ((member instanceof vscode.Uri) ? member.path : member).toLowerCase(); + for (const group of vscode.window.tabGroups.all) { + memberTab = group.tabs.find(tab => + (tab.input instanceof vscode.TabInputText) + && (tab.input.uri.scheme === `member`) + && (tab.input.uri.path.toLowerCase() === memberPath) + ); + if (memberTab) + break; + } + return memberTab; + } + /** * Fixes an SQL statement to make it compatible with db2 CLI program QZDFMDB2. * - Changes `@clCommand` statements into Call `QSYS2.QCMDEX('clCommand')` procedure calls diff --git a/src/views/objectBrowser.ts b/src/views/objectBrowser.ts index 079aa31ee..bdc9c7355 100644 --- a/src/views/objectBrowser.ts +++ b/src/views/objectBrowser.ts @@ -320,24 +320,6 @@ class ObjectBrowserMemberItem extends ObjectBrowserItem implements MemberItem { } } -// For a given member's path or uri, find an open tab (if any) -// where that member is being edited. -// Assume that the member is not open in more than one tab. -function findMemberTab(member: vscode.Uri | string): vscode.Tab | undefined { - let memberTab: vscode.Tab | undefined; - const memberPath = ((member instanceof Uri) ? member.path : member).toLowerCase(); - for (const group of vscode.window.tabGroups.all) { - memberTab = group.tabs.find(tab => - (tab.input instanceof vscode.TabInputText) - && (tab.input.uri.scheme === `member`) - && (tab.input.uri.path.toLowerCase() === memberPath) - ); - if(memberTab) - break; - } - return memberTab; -} - export function initializeObjectBrowser(context: vscode.ExtensionContext) { const objectBrowser = new ObjectBrowser(); const objectTreeViewer = vscode.window.createTreeView( @@ -660,7 +642,7 @@ export function initializeObjectBrowser(context: vscode.ExtensionContext) { let newNameOK; // Check if the member is currently open in an editor tab. - const oldMemberTab = findMemberTab(oldUri); + const oldMemberTab = Tools.findMemberTab(oldUri); // If the member is currently open in an editor tab, and // the member has unsaved changes, then prevent the renaming operation. From 5f08ff6a63689771e7fc3a14c0b646d757577398 Mon Sep 17 00:00:00 2001 From: Steph Beneschan <36548653+steph-beneschan-256@users.noreply.github.com> Date: Wed, 6 Mar 2024 19:39:59 -0800 Subject: [PATCH 4/8] Apply member rename/move behavior to streamfiles * The IFS browser no longer allows the user to rename/move a file if the user is currently editing the file and there are unsaved changes * When the user renames or moves an IFS file, all tabs where the file was open are now closed and reopened with the new file path * Rewrote function findMemberTab as findUriTabs; the function now accepts URIs for members or streamfiles, and returns all tabs in which the resource is open (instead of just one) * Added helper function areEquivalentUris to check whether two URIs (both assumed to represent members or streamfiles) are equivalent --- src/api/Tools.ts | 29 ++++++++++++++++------------- src/views/ifsBrowser.ts | 21 +++++++++++++++++++++ src/views/objectBrowser.ts | 22 +++++++++++++--------- 3 files changed, 50 insertions(+), 22 deletions(-) diff --git a/src/api/Tools.ts b/src/api/Tools.ts index 2672f993c..054ce5b7a 100644 --- a/src/api/Tools.ts +++ b/src/api/Tools.ts @@ -261,6 +261,12 @@ export namespace Tools { } } + /** + * Check whether two given uris point to the same file/member + */ + export function areEquivalentUris(uriA: vscode.Uri, uriB: vscode.Uri) { + return uriStringWithoutFragment(uriA) === uriStringWithoutFragment(uriB); + } /** * We do this to find previously opened files with the same path, but different case OR readonly flags. @@ -283,23 +289,20 @@ export namespace Tools { } /** - * For a given member's path or uri, find an open tab (if any) - * where that member is being edited. - * Assume that the member is not open in more than one tab. + * Given the uri of a member or other resource, find all + * (if any) open tabs where that resource is being edited. */ - export function findMemberTab(member: vscode.Uri | string): vscode.Tab | undefined { - let memberTab: vscode.Tab | undefined; - const memberPath = ((member instanceof vscode.Uri) ? member.path : member).toLowerCase(); + export function findUriTabs(uriToFind: vscode.Uri): vscode.Tab[] { + let resourceTabs: vscode.Tab[] = []; for (const group of vscode.window.tabGroups.all) { - memberTab = group.tabs.find(tab => + group.tabs.filter(tab => (tab.input instanceof vscode.TabInputText) - && (tab.input.uri.scheme === `member`) - && (tab.input.uri.path.toLowerCase() === memberPath) - ); - if (memberTab) - break; + && areEquivalentUris(tab.input.uri, uriToFind) + ).forEach(tab => { + resourceTabs.push(tab); + }); } - return memberTab; + return resourceTabs; } /** diff --git a/src/views/ifsBrowser.ts b/src/views/ifsBrowser.ts index 067534839..532576f32 100644 --- a/src/views/ifsBrowser.ts +++ b/src/views/ifsBrowser.ts @@ -614,6 +614,17 @@ export function initializeIFSBrowser(context: vscode.ExtensionContext) { }), vscode.commands.registerCommand(`code-for-ibmi.moveIFS`, async (node: IFSItem) => { + // Ensure that the file has a defined uri + if (!node.resourceUri) { + vscode.window.showErrorMessage(t("ifsBrowser.moveIFS.errorMessage", t(String(node.contextValue)), "The file path could not be parsed.")); + return; + } + // Check if the streamfile is currently open in an editor tab + const oldFileTabs = Tools.findUriTabs(node.resourceUri); + if (oldFileTabs.find(tab => tab.isDirty)) { + vscode.window.showErrorMessage(t("ifsBrowser.moveIFS.errorMessage", t(String(node.contextValue)), "The file has unsaved changes.")); + return; + } const connection = instance.getConnection(); const config = instance.getConfig(); if (config && connection) { @@ -635,6 +646,16 @@ export function initializeIFSBrowser(context: vscode.ExtensionContext) { Tools.escapePath(node.path), Tools.escapePath(targetPath) )); + // If the file was open in any editor tabs prior to the renaming/movement, + // refresh those tabs to reflect the new file path/name. + // (Directly modifying the label or uri of an open tab is apparently not + // possible with the current VS Code API, so refresh the tab by closing + // it and then opening a new one at the new uri.) + oldFileTabs.forEach((tab) => { + vscode.window.tabGroups.close(tab).then(() => { + vscode.commands.executeCommand(`code-for-ibmi.openEditable`, targetPath); + }) + }) } catch (e) { vscode.window.showErrorMessage(t(`ifsBrowser.moveIFS.errorMessage`, t(String(node.contextValue)), e)); diff --git a/src/views/objectBrowser.ts b/src/views/objectBrowser.ts index f9a469525..1cdd984c4 100644 --- a/src/views/objectBrowser.ts +++ b/src/views/objectBrowser.ts @@ -726,11 +726,11 @@ export function initializeObjectBrowser(context: vscode.ExtensionContext) { let newNameOK; // Check if the member is currently open in an editor tab. - const oldMemberTab = Tools.findMemberTab(oldUri); + const oldMemberTabs = Tools.findUriTabs(oldUri); // If the member is currently open in an editor tab, and // the member has unsaved changes, then prevent the renaming operation. - if(oldMemberTab && oldMemberTab.isDirty) { + if(oldMemberTabs.find(tab => tab.isDirty)){ vscode.window.showErrorMessage(t("objectBrowser.renameMember.errorMessage", "The member has unsaved changes.")); return; } @@ -783,13 +783,17 @@ export function initializeObjectBrowser(context: vscode.ExtensionContext) { } } while (newBasename && !newNameOK) - // If the member was open in an editor tab, refresh that tab. - // (Evidently the VS Code API does not allow us to modify an - // open tab to change its label or its uri, so we have to close - // the existing tab and then open a new one at the updated uri. - if (oldMemberTab && newNameOK && newMemberPath) { - vscode.window.tabGroups.close(oldMemberTab); - vscode.commands.executeCommand(`code-for-ibmi.openEditable`, newMemberPath); + // If the member was open in an editor tab prior to the renaming, + // refresh those tabs to reflect the new member path/name. + // (Directly modifying the label or uri of an open tab is apparently not + // possible with the current VS Code API, so refresh the tab by closing + // it and then opening a new one at the new uri.) + if (newNameOK && newMemberPath) { + oldMemberTabs.forEach((tab) => { + vscode.window.tabGroups.close(tab).then(() => { + vscode.commands.executeCommand(`code-for-ibmi.openEditable`, newMemberPath); + }); + }) } }), From 15c1bf9c1ebbf149be7aed3de6329ad2feb16f57 Mon Sep 17 00:00:00 2001 From: Christian Jorgensen Date: Tue, 28 May 2024 17:21:53 +0200 Subject: [PATCH 5/8] Change rename error text to locale string --- src/locale/ids/da.json | 3 +++ src/locale/ids/en.json | 3 +++ src/locale/ids/fr.json | 3 +++ src/views/ifsBrowser.ts | 4 ++-- src/views/objectBrowser.ts | 2 +- 5 files changed, 12 insertions(+), 3 deletions(-) diff --git a/src/locale/ids/da.json b/src/locale/ids/da.json index 681bbb5ca..5c0278129 100644 --- a/src/locale/ids/da.json +++ b/src/locale/ids/da.json @@ -127,6 +127,8 @@ "enddbgsvr.succeeded": "Debug server er stoppet.", "error": "fejl", "errors": "fejl", + "file.path.not.parsed": "Filstien kunne ikke fortolkes.", + "file.unsaved.changes": "Filen har ikke-gemte ændringer.", "generate.certificate": "Generer service certifikat", "helpView.getStarted": "Dokumentation", "helpView.officialForum": "Forum", @@ -238,6 +240,7 @@ "login.title.create": "Forbind til IBM i", "login.title.edit": "Login Indstillinger: \"{0}\"", "MAX_HEAP_SIZE": "Maksimalt tilladt memory", + "member.has.unsaved.changes": "Memberet har ikke-gemte ændringer.", "members": "Members", "MEMORY_POOL": "Memory pulje", "modified": "Ændret", diff --git a/src/locale/ids/en.json b/src/locale/ids/en.json index dc64c0236..78b6184a9 100644 --- a/src/locale/ids/en.json +++ b/src/locale/ids/en.json @@ -127,6 +127,8 @@ "enddbgsvr.succeeded": "Debug server stopped.", "error": "error", "errors": "errors", + "file.path.not.parsed": "The file path could not be parsed.", + "file.unsaved.changes": "The file has unsaved changes.", "generate.certificate": "Generate service certificate", "helpView.getStarted": "Get started", "helpView.officialForum": "Open official Forum", @@ -238,6 +240,7 @@ "login.title.create": "Connect to IBM i", "login.title.edit": "Login Settings: \"{0}\"", "MAX_HEAP_SIZE": "Maximum allowed memory", + "member.has.unsaved.changes": "The member has unsaved changes.", "members": "Members", "MEMORY_POOL": "Memory pool", "modified": "Modified", diff --git a/src/locale/ids/fr.json b/src/locale/ids/fr.json index 49fa31ef0..cb76d4b84 100644 --- a/src/locale/ids/fr.json +++ b/src/locale/ids/fr.json @@ -127,6 +127,8 @@ "enddbgsvr.succeeded": "Serveur de débogage arrêté.", "error": "erreur", "errors": "erreurs", + "file.path.not.parsed": "The file path could not be parsed.", + "file.unsaved.changes": "The file has unsaved changes.", "generate.certificate": "Generer le certificat du service", "helpView.getStarted": "Pour commencer", "helpView.officialForum": "Forum officiel", @@ -238,6 +240,7 @@ "login.title.create": "Connect to IBM i", "login.title.edit": "paramètres d'authentification: \"{0}\"", "MAX_HEAP_SIZE": "Mémoire maximum allouée", + "member.has.unsaved.changes": "The member has unsaved changes.", "members": "Membres", "MEMORY_POOL": "Pool mémoire", "modified": "Modifié", diff --git a/src/views/ifsBrowser.ts b/src/views/ifsBrowser.ts index 205069c13..fec351480 100644 --- a/src/views/ifsBrowser.ts +++ b/src/views/ifsBrowser.ts @@ -618,13 +618,13 @@ export function initializeIFSBrowser(context: vscode.ExtensionContext) { vscode.commands.registerCommand(`code-for-ibmi.moveIFS`, async (node: IFSItem) => { // Ensure that the file has a defined uri if (!node.resourceUri) { - vscode.window.showErrorMessage(t("ifsBrowser.moveIFS.errorMessage", t(String(node.contextValue)), "The file path could not be parsed.")); + vscode.window.showErrorMessage(t("ifsBrowser.moveIFS.errorMessage", t(String(node.contextValue)), t("file.path.not.parsed"))); return; } // Check if the streamfile is currently open in an editor tab const oldFileTabs = Tools.findUriTabs(node.resourceUri); if (oldFileTabs.find(tab => tab.isDirty)) { - vscode.window.showErrorMessage(t("ifsBrowser.moveIFS.errorMessage", t(String(node.contextValue)), "The file has unsaved changes.")); + vscode.window.showErrorMessage(t("ifsBrowser.moveIFS.errorMessage", t(String(node.contextValue)), t("file.unsaved.changes"))); return; } const connection = instance.getConnection(); diff --git a/src/views/objectBrowser.ts b/src/views/objectBrowser.ts index 89d6b7007..ae46e1b85 100644 --- a/src/views/objectBrowser.ts +++ b/src/views/objectBrowser.ts @@ -748,7 +748,7 @@ export function initializeObjectBrowser(context: vscode.ExtensionContext) { // If the member is currently open in an editor tab, and // the member has unsaved changes, then prevent the renaming operation. if(oldMemberTabs.find(tab => tab.isDirty)){ - vscode.window.showErrorMessage(t("objectBrowser.renameMember.errorMessage", "The member has unsaved changes.")); + vscode.window.showErrorMessage(t("objectBrowser.renameMember.errorMessage", t("member.has.unsaved.changes"))); return; } From d4ccea3a368b7713b8d32aafca3cadda54121c78 Mon Sep 17 00:00:00 2001 From: Christian Jorgensen Date: Tue, 28 May 2024 17:54:29 +0200 Subject: [PATCH 6/8] Update src/locale/ids/fr.json MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Sébastien Julliand --- src/locale/ids/fr.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/locale/ids/fr.json b/src/locale/ids/fr.json index cb76d4b84..d0fb06742 100644 --- a/src/locale/ids/fr.json +++ b/src/locale/ids/fr.json @@ -127,7 +127,7 @@ "enddbgsvr.succeeded": "Serveur de débogage arrêté.", "error": "erreur", "errors": "erreurs", - "file.path.not.parsed": "The file path could not be parsed.", + "file.path.not.parsed": "Le chemin du fichier n'a pas pu être analysé.", "file.unsaved.changes": "The file has unsaved changes.", "generate.certificate": "Generer le certificat du service", "helpView.getStarted": "Pour commencer", From 79a14d4351a22b2107aef80efd6d2338aadd8ece Mon Sep 17 00:00:00 2001 From: Christian Jorgensen Date: Tue, 28 May 2024 17:54:36 +0200 Subject: [PATCH 7/8] Update src/locale/ids/fr.json MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Sébastien Julliand --- src/locale/ids/fr.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/locale/ids/fr.json b/src/locale/ids/fr.json index d0fb06742..ca6aee142 100644 --- a/src/locale/ids/fr.json +++ b/src/locale/ids/fr.json @@ -128,7 +128,7 @@ "error": "erreur", "errors": "erreurs", "file.path.not.parsed": "Le chemin du fichier n'a pas pu être analysé.", - "file.unsaved.changes": "The file has unsaved changes.", + "file.unsaved.changes": "Le fichier a des changements non sauvegardés.", "generate.certificate": "Generer le certificat du service", "helpView.getStarted": "Pour commencer", "helpView.officialForum": "Forum officiel", From 7446c9ab2817e7db5ed92035cdeb3acbea6f1c30 Mon Sep 17 00:00:00 2001 From: Christian Jorgensen Date: Tue, 28 May 2024 17:54:53 +0200 Subject: [PATCH 8/8] Update src/locale/ids/fr.json MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Sébastien Julliand --- src/locale/ids/fr.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/locale/ids/fr.json b/src/locale/ids/fr.json index ca6aee142..321ec2ed9 100644 --- a/src/locale/ids/fr.json +++ b/src/locale/ids/fr.json @@ -240,7 +240,7 @@ "login.title.create": "Connect to IBM i", "login.title.edit": "paramètres d'authentification: \"{0}\"", "MAX_HEAP_SIZE": "Mémoire maximum allouée", - "member.has.unsaved.changes": "The member has unsaved changes.", + "member.has.unsaved.changes": "Le membre a des changements non sauvegardés.", "members": "Membres", "MEMORY_POOL": "Pool mémoire", "modified": "Modifié",