Index: gc_lang/fr/build.py ================================================================== --- gc_lang/fr/build.py +++ gc_lang/fr/build.py @@ -11,10 +11,11 @@ import helpers def build (sLang, dVars): "complementary build launched from make.py" + dVars['webextOptionsHTML'] = _createOptionsForWebExtension(dVars) createWebExtension(sLang, dVars) convertWebExtensionForChrome(sLang, dVars) createMailExtension(sLang, dVars) createNodeJSPackage(sLang) @@ -23,11 +24,10 @@ "create Web-extension" print("> Building WebExtension for Firefox") helpers.createCleanFolder("_build/webext/"+sLang) dir_util.copy_tree("gc_lang/"+sLang+"/webext/", "_build/webext/"+sLang) dir_util.copy_tree("grammalecte-js", "_build/webext/"+sLang+"/grammalecte") - dVars['webextOptionsHTML'] = _createOptionsForWebExtension(dVars) helpers.copyAndFileTemplate("_build/webext/"+sLang+"/manifest.json", "_build/webext/"+sLang+"/manifest.json", dVars) helpers.copyAndFileTemplate("_build/webext/"+sLang+"/panel/main.html", "_build/webext/"+sLang+"/panel/main.html", dVars) with helpers.CD("_build/webext/"+sLang): os.system("web-ext build") # Copy Firefox zip extension to _build @@ -70,38 +70,30 @@ spfZip = "_build/" + dVars['tb_identifier'] + "-v" + dVars['version'] + '.mailext.xpi' hZip = zipfile.ZipFile(spfZip, mode='w', compression=zipfile.ZIP_DEFLATED) _copyGrammalecteJSPackageInZipFile(hZip, sLang) for spf in ["LICENSE.txt", "LICENSE.fr.txt"]: hZip.write(spf) - dVars = _createOptionsForThunderbird(dVars) + helpers.addFolderToZipAndFileFile(hZip, "gc_lang/"+sLang+"/webext/gce_worker.js") helpers.addFolderToZipAndFileFile(hZip, "gc_lang/"+sLang+"/mailext", "", dVars, True) + helpers.addFolderToZipAndFileFile(hZip, "gc_lang/"+sLang+"/webext/3rd", "3rd", dVars, True) + helpers.addFolderToZipAndFileFile(hZip, "gc_lang/"+sLang+"/webext/_locales", "_locales", dVars, True) + helpers.addFolderToZipAndFileFile(hZip, "gc_lang/"+sLang+"/webext/content_scripts", "content_scripts", dVars, True) + helpers.addFolderToZipAndFileFile(hZip, "gc_lang/"+sLang+"/webext/fonts", "fonts", dVars, True) + helpers.addFolderToZipAndFileFile(hZip, "gc_lang/"+sLang+"/webext/img", "img", dVars, True) + helpers.addFolderToZipAndFileFile(hZip, "gc_lang/"+sLang+"/webext/panel", "panel", dVars, True) hZip.close() - #spExtension = dVars['win_tb_debug_extension_path'] if platform.system() == "Windows" else dVars['linux_tb_debug_extension_path'] - #if os.path.isdir(spExtension): - # file_util.copy_file(spfZip, spExtension + "/" + dVars['tb_identifier']+ ".xpi") # Filename for TB is just - # print(f"TB extension copied in <{spExtension}>") - #spExtension = dVars['win_tb_beta_extension_path'] if platform.system() == "Windows" else dVars['linux_tb_beta_extension_path'] - #if os.path.isdir(spExtension): - # print(f"TB extension copied in <{spExtension}>") - # file_util.copy_file(spfZip, spExtension + "/" + dVars['tb_identifier']+ ".xpi") # Filename for TB is just - - -def _createOptionsForThunderbird (dVars): - dVars['sXULTabs'] = "" - dVars['sXULTabPanels'] = "" - # dialog options - for sSection, lOpt in dVars['lStructOpt']: - dVars['sXULTabs'] += ' \n' - dVars['sXULTabPanels'] += ' \n \n' - # translation data - for sLang in dVars['dOptLabel'].keys(): - dVars['gc_options_labels_'+sLang] = "\n".join( [ "' for sOpt in dVars['dOptLabel'][sLang] ] ) - return dVars + # Note about copying Thunderbird extension directly into the profile: + # In Options > Configuration editor (about:config), deactivate option + # If is changed, you must reinstall the extension manually + spExtension = dVars['win_tb_debug_extension_path'] if platform.system() == "Windows" else dVars['linux_tb_debug_extension_path'] + if os.path.isdir(spExtension): + file_util.copy_file(spfZip, f"{spExtension}/{dVars['tb_identifier']}.xpi") # Filename for TB is just + print(f"Thunderbird extension copied in <{spExtension}>") + spExtension = dVars['win_tb_beta_extension_path'] if platform.system() == "Windows" else dVars['linux_tb_beta_extension_path'] + if os.path.isdir(spExtension): + file_util.copy_file(spfZip, f"{spExtension}/{dVars['tb_identifier']}.xpi") # Filename for TB is just + print(f"Thunderbird extension copied in <{spExtension}>") def _copyGrammalecteJSPackageInZipFile (hZip, sLang, sAddPath=""): for sf in os.listdir("grammalecte-js"): if not os.path.isdir("grammalecte-js/"+sf): Index: gc_lang/fr/config.ini ================================================================== --- gc_lang/fr/config.ini +++ gc_lang/fr/config.ini @@ -57,11 +57,10 @@ tb_name = Grammalecte [fr] win_tb_path = C:\Program Files\Mozilla Thunderbird\thunderbird.exe win_tb_beta_path = C:\Program Files\Thunderbird Daily\thunderbird.exe linux_tb_path = /usr/bin/thunderbird linux_tb_beta_path = /usr/bin/thunderbird -# useless now win_tb_debug_extension_path = D:\_temp\tb-debug.profile\extensions linux_tb_debug_extension_path = ~/tb-debug.profile/extensions win_tb_beta_extension_path = D:\_temp\tb-beta.profile\extensions linux_tb_beta_extension_path = ~/tb-beta.profile/extensions # Set Thunderbird folder in your PATH variable DELETED gc_lang/fr/mailext/README.txt Index: gc_lang/fr/mailext/README.txt ================================================================== --- gc_lang/fr/mailext/README.txt +++ /dev/null @@ -1,14 +0,0 @@ - -= GRAMMALECTE = - -French grammar checker -By Olivier R. (olivier /at/ grammalecte /dot/ net) - -Website: https://grammalecte.net/ - -License: GPL 3 -- http://www.gnu.org/copyleft/gpl.html - -Grammalecte for Firefox is a derivative tool born from the version -for LibreOffice written in Python. - -Written in JavaScript ES6/ES7. Index: gc_lang/fr/mailext/background.js ================================================================== --- gc_lang/fr/mailext/background.js +++ gc_lang/fr/mailext/background.js @@ -1,10 +1,29 @@ // Background +/* jshint esversion:6, -W097 */ +/* jslint esversion:6 */ +/* global helpers, showError, Worker, chrome, console */ + "use strict"; -// Draft for later + +console.log("[Grammalecte] background"); + + +// Chrome don’t follow the W3C specification: +// https://browserext.github.io/browserext/ +let bChrome = false; +let bThunderbird = false; +if (typeof(browser) !== "object") { + var browser = chrome; + bChrome = true; +} +if (typeof(messenger) === "object") { + bThunderbird = true; +} + const oWorkerHandler = { xGCEWorker: null, nLastTimeWorkerResponse: 0, // milliseconds since 1970-01-01 @@ -107,20 +126,63 @@ const oInitHandler = { initUIOptions: function () { + if (bChrome) { + browser.storage.local.get("ui_options", this._initUIOptions); + browser.storage.local.get("autorefresh_option", this._initUIOptions); + return; + } browser.storage.local.get("ui_options").then(this._initUIOptions, showError); browser.storage.local.get("autorefresh_option").then(this._initUIOptions, showError); }, initGrammarChecker: function () { + if (bChrome) { + browser.storage.local.get("gc_options", this._initGrammarChecker); + browser.storage.local.get("main_dic_name", this._setSpellingDictionaries); + browser.storage.local.get("personal_dictionary", this._setSpellingDictionaries); + browser.storage.local.get("community_dictionary", this._setSpellingDictionaries); + browser.storage.local.get("oPersonalDictionary", this._setSpellingDictionaries); // deprecated + browser.storage.local.get("sc_options", this._initSCOptions); + return; + } browser.storage.local.get("gc_options").then(this._initGrammarChecker, showError); + browser.storage.local.get("main_dic_name", this._setSpellingDictionaries); browser.storage.local.get("personal_dictionary").then(this._setSpellingDictionaries, showError); browser.storage.local.get("community_dictionary").then(this._setSpellingDictionaries, showError); + browser.storage.local.get("oPersonalDictionary").then(this._setSpellingDictionaries, showError); // deprecated browser.storage.local.get("sc_options").then(this._initSCOptions, showError); }, + + registerComposeScripts: async function () { + // For Thunderbird only + if (bThunderbird) { + let xRegisteredScripts = await browser.composeScripts.register({ + /*css: [ + // Any number of code or file objects could be listed here. + { code: "body { background-color: red; }" }, + { file: "compose.css" }, + ],*/ + js: [ + // Any number of code or file objects could be listed here. + //{ code: `document.body.textContent = "Hey look, the script ran!";` }, + { file: "content_scripts/html_src.js" }, + { file: "content_scripts/panel.js" }, + { file: "grammalecte/fr/textformatter.js" }, + { file: "content_scripts/panel_tf.js" }, + { file: "content_scripts/panel_gc.js" }, + { file: "content_scripts/message_box.js" }, + { file: "content_scripts/menu.js" }, + { file: "content_scripts/init.js" } + ] + }); + // To unregister scripts: + // await xRegisteredScripts.unregister(); + } + }, _initUIOptions: function (oSavedOptions) { if (!oSavedOptions.hasOwnProperty("ui_options")) { browser.storage.local.set({"ui_options": { textarea: true, @@ -150,10 +212,20 @@ showError(e); } }, _setSpellingDictionaries: function (oData) { + if (oData.hasOwnProperty("oPersonalDictionary")) { + // deprecated (to be removed in 2020) + console.log("personal dictionary migration"); + browser.storage.local.set({ "personal_dictionary": oData["oPersonalDictionary"] }); + oWorkerHandler.xGCEWorker.postMessage({ sCommand: "setDictionary", oParam: { sDictionary: "personal", oDict: oData["oPersonalDictionary"] }, oInfo: {} }); + browser.storage.local.remove("oPersonalDictionary"); + } + if (oData.hasOwnProperty("main_dic_name")) { + oWorkerHandler.xGCEWorker.postMessage({ sCommand: "setDictionary", oParam: { sDictionary: "main", oDict: oData["main_dic_name"] }, oInfo: {sExtPath: browser.extension.getURL("")} }); + } if (oData.hasOwnProperty("community_dictionary")) { oWorkerHandler.xGCEWorker.postMessage({ sCommand: "setDictionary", oParam: { sDictionary: "community", oDict: oData["community_dictionary"] }, oInfo: {} }); } if (oData.hasOwnProperty("personal_dictionary")) { oWorkerHandler.xGCEWorker.postMessage({ sCommand: "setDictionary", oParam: { sDictionary: "personal", oDict: oData["personal_dictionary"] }, oInfo: {} }); @@ -170,20 +242,224 @@ oWorkerHandler.xGCEWorker.postMessage({ sCommand: "setDictionaryOnOff", oParam: { sDictionary: "personal", bActivate: true }, oInfo: {} }); } else { oWorkerHandler.xGCEWorker.postMessage({ sCommand: "setDictionaryOnOff", oParam: { sDictionary: "community", bActivate: oData.sc_options["community"] }, oInfo: {} }); oWorkerHandler.xGCEWorker.postMessage({ sCommand: "setDictionaryOnOff", oParam: { sDictionary: "personal", bActivate: oData.sc_options["personal"] }, oInfo: {} }); } - } + }, } // start the Worker for the GC oWorkerHandler.start(); // init the options stuff and start the GC oInitHandler.initUIOptions(); oInitHandler.initGrammarChecker(); +oInitHandler.registerComposeScripts(); // Thunderbird only + + +// When the extension is installed or updated +browser.runtime.onInstalled.addListener(function (oDetails) { + // launched at installation or update + // https://developer.mozilla.org/fr/Add-ons/WebExtensions/API/runtime/onInstalled + if (oDetails.reason == "update" || oDetails.reason == "installed") { + // todo + //browser.tabs.create({url: "http://grammalecte.net"}); + } +}); + + + +/* + Ports from content-scripts +*/ + +let dConnx = new Map(); + + +/* + Messages from the extension (not the Worker) +*/ +function handleMessage (oRequest, xSender, sendResponse) { + // message from panels + //console.log(xSender); + let {sCommand, oParam, oInfo} = oRequest; + switch (sCommand) { + case "getOptions": + case "getDefaultOptions": + case "setOptions": + case "setOption": + case "resetOptions": + case "textToTest": + case "fullTests": + case "setDictionary": + case "setDictionaryOnOff": + oWorkerHandler.xGCEWorker.postMessage(oRequest); + break; + case "restartWorker": + oWorkerHandler.restart(oParam["nDelayLimit"]); + break; + case "openURL": + browser.tabs.create({url: oParam.sURL}); + break; + case "openConjugueurTab": + openConjugueurTab(); + break; + case "openLexiconEditor": + openLexiconEditor(oParam["dictionary"]); + break; + case "openDictionaries": + openDictionaries(); + break; + default: + console.log("[background] Unknown command: " + sCommand); + console.log(oRequest); + } + //sendResponse({response: "response from background script"}); +} + +browser.runtime.onMessage.addListener(handleMessage); + + +function handleConnexion (xPort) { + // Messages from tabs + let iPortId = xPort.sender.tab.id; // identifier for the port: each port can be found at dConnx[iPortId] + dConnx.set(iPortId, xPort); + xPort.onMessage.addListener(function (oRequest) { + let {sCommand, oParam, oInfo} = oRequest; + switch (sCommand) { + case "ping": + //console.log("[background] ping"); + xPort.postMessage({sActionDone: "ping", result: null, bInfo: null, bEnd: true, bError: false}); + break; + case "parse": + case "parseAndSpellcheck": + case "parseAndSpellcheck1": + case "parseFull": + case "getListOfTokens": + case "getSpellSuggestions": + case "getVerb": + oRequest.oInfo.iReturnPort = iPortId; // we pass the id of the return port to receive answer + oWorkerHandler.xGCEWorker.postMessage(oRequest); + break; + case "restartWorker": + oWorkerHandler.restart(oParam["nDelayLimit"]); + break; + case "openURL": + browser.tabs.create({url: oParam.sURL}); + break; + case "openConjugueurTab": + openConjugueurTab(); + break; + case "openConjugueurWindow": + openConjugueurWindow(); + break; + case "openLexiconEditor": + openLexiconEditor(); + break; + default: + console.log("[background] Unknown command: " + sCommand); + console.log(oRequest); + } + }); + //xPort.postMessage({sActionDone: "newId", result: iPortId}); + xPort.postMessage({sActionDone: "init", sUrl: browser.extension.getURL("")}); +} + +browser.runtime.onConnect.addListener(handleConnexion); + + +/* + Context Menu + (not for MailExtension) +*/ +if (!bThunderbird) { + // Analyze + browser.contextMenus.create({ id: "grammar_checker_editable", title: "Analyser cette zone de texte", contexts: ["editable"] }); + browser.contextMenus.create({ id: "grammar_checker_selection", title: "Analyser la sélection", contexts: ["selection"] }); + browser.contextMenus.create({ id: "grammar_checker_iframe", title: "Analyser le contenu de ce cadre", contexts: ["frame"] }); + browser.contextMenus.create({ id: "grammar_checker_page", title: "Analyser la page", contexts: ["all"] }); + browser.contextMenus.create({ id: "separator_tools", type: "separator", contexts: ["all"] }); + // Tools + browser.contextMenus.create({ id: "conjugueur_tab", title: "Conjugueur [onglet]", contexts: ["all"] }); + browser.contextMenus.create({ id: "conjugueur_window", title: "Conjugueur [fenêtre]", contexts: ["all"] }); + //browser.contextMenus.create({ id: "dictionaries", title: "Dictionnaires", contexts: ["all"] }); + browser.contextMenus.create({ id: "lexicon_editor", title: "Éditeur lexical", contexts: ["all"] }); + + browser.contextMenus.onClicked.addListener(function (xInfo, xTab) { + // xInfo = https://developer.mozilla.org/en-US/Add-ons/WebExtensions/API/contextMenus/OnClickData + // xTab = https://developer.mozilla.org/en-US/Add-ons/WebExtensions/API/tabs/Tab + // confusing: no way to get the node where we click?! + switch (xInfo.menuItemId) { + // analyze + case "grammar_checker_editable": + case "grammar_checker_page": + sendCommandToTab(xTab.id, xInfo.menuItemId); + break; + case "grammar_checker_iframe": + sendCommandToTab(xTab.id, xInfo.menuItemId, xInfo.frameId); + break; + case "grammar_checker_selection": + sendCommandToTab(xTab.id, xInfo.menuItemId, xInfo.selectionText); + break; + // tools + case "conjugueur_window": + openConjugueurWindow(); + break; + case "conjugueur_tab": + openConjugueurTab(); + break; + case "lexicon_editor": + openLexiconEditor(); + break; + case "dictionaries": + openDictionaries(); + break; + default: + console.log("[Background] Unknown menu id: " + xInfo.menuItemId); + console.log(xInfo); + console.log(xTab); + } + }); +} + + + +/* + Keyboard shortcuts +*/ +browser.commands.onCommand.addListener(function (sCommand) { + switch (sCommand) { + case "grammar_checker": + sendCommandToCurrentTab("shortcutGrammarChecker"); + break; + case "conjugueur_tab": + openConjugueurTab(); + break; + case "lexicon_editor": + openLexiconEditor(); + break; + case "dictionaries": + openDictionaries(); + break; + } +}); + + +/* + Tabs +*/ +let nTabLexiconEditor = null; +let nTabDictionaries = null; +let nTabConjugueur = null; +browser.tabs.onRemoved.addListener(function (nTabId, xRemoveInfo) { + switch (nTabId) { + case nTabLexiconEditor: nTabLexiconEditor = null; break; + case nTabDictionaries: nTabDictionaries = null; break; + case nTabConjugueur: nTabConjugueur = null; break; + } +}); /* Actions */ @@ -192,11 +468,124 @@ if (dOptions instanceof Map) { dOptions = helpers.mapToObject(dOptions); } browser.storage.local.set({"gc_options": dOptions}); } + +function sendCommandToTab (iTab, sCommand, result=null) { + let xTabPort = dConnx.get(iTab); + xTabPort.postMessage({sActionDone: sCommand, result: result, oInfo: null, bEnd: false, bError: false}); +} + +function sendCommandToCurrentTab (sCommand) { + if (bChrome) { + browser.tabs.query({ currentWindow: true, active: true }, (lTabs) => { + for (let xTab of lTabs) { + //console.log(xTab); + browser.tabs.sendMessage(xTab.id, {sActionRequest: sCommand}); + } + }); + return; + } + browser.tabs.query({ currentWindow: true, active: true }).then((lTabs) => { + for (let xTab of lTabs) { + //console.log(xTab); + browser.tabs.sendMessage(xTab.id, {sActionRequest: sCommand}); + } + }, showError); +} + +function sendCommandToAllTabs (sCommand) { + for (let [iTab, xTabPort] of dConnx.entries()) { + xTabPort.postMessage({sActionDone: sCommand, result: null, oInfo: null, bEnd: false, bError: false}); + } +} + +function openLexiconEditor (sName="__personal__") { + if (nTabLexiconEditor === null) { + if (bChrome) { + browser.tabs.create({ + url: browser.extension.getURL("panel/lex_editor.html") + }, onLexiconEditorOpened); + return; + } + let xLexEditor = browser.tabs.create({ + url: browser.extension.getURL("panel/lex_editor.html") + }); + xLexEditor.then(onLexiconEditorOpened, showError); + } + else { + browser.tabs.update(nTabLexiconEditor, {active: true}); + } +} + +function onLexiconEditorOpened (xTab) { + nTabLexiconEditor = xTab.id; +} + +function openDictionaries () { + if (nTabDictionaries === null) { + if (bChrome) { + browser.tabs.create({ + url: browser.extension.getURL("panel/dictionaries.html") + }, onDictionariesOpened); + return; + } + let xLexEditor = browser.tabs.create({ + url: browser.extension.getURL("panel/dictionaries.html") + }); + xLexEditor.then(onDictionariesOpened, showError); + } + else { + browser.tabs.update(nTabDictionaries, {active: true}); + } +} + +function onDictionariesOpened (xTab) { + nTabDictionaries = xTab.id; +} + +function openConjugueurTab () { + if (nTabConjugueur === null) { + if (bChrome) { + browser.tabs.create({ + url: browser.extension.getURL("panel/conjugueur.html") + }, onConjugueurOpened); + return; + } + let xConjTab = browser.tabs.create({ + url: browser.extension.getURL("panel/conjugueur.html") + }); + xConjTab.then(onConjugueurOpened, showError); + } + else { + browser.tabs.update(nTabConjugueur, {active: true}); + } +} + +function onConjugueurOpened (xTab) { + nTabConjugueur = xTab.id; +} + +function openConjugueurWindow () { + if (bChrome) { + browser.windows.create({ + url: browser.extension.getURL("panel/conjugueur.html"), + type: "popup", + width: 710, + height: 980 + }); + return; + } + let xConjWindow = browser.windows.create({ + url: browser.extension.getURL("panel/conjugueur.html"), + type: "popup", + width: 710, + height: 980 + }); +} function showError (e) { console.error(e); //console.error(e.fileName + "\n" + e.name + "\nline: " + e.lineNumber + "\n" + e.message); } DELETED gc_lang/fr/mailext/chrome.manifest Index: gc_lang/fr/mailext/chrome.manifest ================================================================== --- gc_lang/fr/mailext/chrome.manifest +++ /dev/null @@ -1,9 +0,0 @@ -# https://developer.mozilla.org/en-US/docs/Chrome_Registration -content grammarchecker content/ -content promiseworker ./worker/ -resource grammalecte ./grammalecte/ -locale grammarchecker fr locale/fr/ -locale grammarchecker en locale/en/ -skin grammarchecker classic/1.0 skin/ -overlay chrome://messenger/content/messengercompose/messengercompose.xul chrome://grammarchecker/content/overlay.xul -style chrome://messenger/content/customizeToolbar.xul chrome://grammarchecker/content/overlay.css DELETED gc_lang/fr/mailext/content/about.css Index: gc_lang/fr/mailext/content/about.css ================================================================== --- gc_lang/fr/mailext/content/about.css +++ /dev/null @@ -1,46 +0,0 @@ -/* CSS */ - -.descr { - font-size: 18px; - font-weight: bold; - text-align: center; -} - -.stdlabel { - font-size: 16px; - text-align: center; -} - -#website { - font-size: 16px; - font-weight: bold; - color: hsl(210, 50%, 50%); - text-align: center; - cursor: pointer; -} - -#contrib { - font-size: 16px; - text-align: center; - color: hsl(210, 50%, 50%); - cursor: pointer; -} - - -/* - TB Next: fix dialogheaders -*/ -dialogheader { - -moz-binding: url("chrome://messenger/content/generalBindings.xml#dialogheader"); - margin: 0 5px 5px; - border: 1px solid ThreeDDarkShadow; - padding: 5px 8px; - background-color: Highlight; - color: HighlightText; -} - -.dialogheader-title { - margin: 0 !important; - font-size: larger; - font-weight: bold; -} DELETED gc_lang/fr/mailext/content/about.js Index: gc_lang/fr/mailext/content/about.js ================================================================== --- gc_lang/fr/mailext/content/about.js +++ /dev/null @@ -1,35 +0,0 @@ -// JavaScript - -const Cc = Components.classes; -const Ci = Components.interfaces; -//const Cu = Components.utils; - - -function openInBrowserURL (sURL) { - // method found in S3.Google.Translator - try { - openURL(sURL); - // Works in overlay.js, but not here… Seems there is no documentation available about this feature on Mozilla.org - } - catch (e) { - console.error(e); - //Cu.reportError(e); - } -} - -function openInTabURL (sURL) { - // method found in S3.Google.Translator - try { - let xWM = Cc["@mozilla.org/appshell/window-mediator;1"].getService(Ci.nsIWindowMediator); - let xWin = xWM.getMostRecentWindow("mail:3pane"); - let xTabmail = xWin.document.getElementById('tabmail'); - xWin.focus(); - if (xTabmail) { - xTabmail.openTab('contentTab', { contentPage: sURL }); - } - } - catch (e) { - console.error(e); - //Cu.reportError(e); - } -} DELETED gc_lang/fr/mailext/content/about.xul Index: gc_lang/fr/mailext/content/about.xul ================================================================== --- gc_lang/fr/mailext/content/about.xul +++ /dev/null @@ -1,38 +0,0 @@ - - - - - - - - - - - - - - -