Started support for translations support.

Sun, 15 Nov 2020 19:53:56 +0100

author
Detlev Offenbach <detlev@die-offenbachs.de>
date
Sun, 15 Nov 2020 19:53:56 +0100
changeset 11
da6ef8ab8268
parent 10
506c78268b18
child 12
68ee221cd0cb

Started support for translations support.

PluginFlask.e4p file | annotate | diff | comparison | revisions
PluginProjectFlask.py file | annotate | diff | comparison | revisions
ProjectFlask/ConfigurationPage/FlaskPage.py file | annotate | diff | comparison | revisions
ProjectFlask/ConfigurationPage/FlaskPage.ui file | annotate | diff | comparison | revisions
ProjectFlask/FlaskBabelDetector.py file | annotate | diff | comparison | revisions
ProjectFlask/FlaskCommandDialog.py file | annotate | diff | comparison | revisions
ProjectFlask/Project.py file | annotate | diff | comparison | revisions
ProjectFlask/RunServerDialog.py file | annotate | diff | comparison | revisions
__init__.py file | annotate | diff | comparison | revisions
diff -r 506c78268b18 -r da6ef8ab8268 PluginFlask.e4p
--- a/PluginFlask.e4p	Sun Nov 15 17:35:48 2020 +0100
+++ b/PluginFlask.e4p	Sun Nov 15 19:53:56 2020 +0100
@@ -18,6 +18,7 @@
     <Source>ProjectFlask/AnsiTools.py</Source>
     <Source>ProjectFlask/ConfigurationPage/FlaskPage.py</Source>
     <Source>ProjectFlask/ConfigurationPage/__init__.py</Source>
+    <Source>ProjectFlask/FlaskBabelDetector.py</Source>
     <Source>ProjectFlask/FlaskCommandDialog.py</Source>
     <Source>ProjectFlask/Project.py</Source>
     <Source>ProjectFlask/RoutesDialog.py</Source>
diff -r 506c78268b18 -r da6ef8ab8268 PluginProjectFlask.py
--- a/PluginProjectFlask.py	Sun Nov 15 17:35:48 2020 +0100
+++ b/PluginProjectFlask.py	Sun Nov 15 19:53:56 2020 +0100
@@ -135,6 +135,8 @@
             
             "FlaskDocUrl": "https://flask.palletsprojects.com",
             
+            "TranslationsEditor": "",
+            
             "UseExternalBrowser": False,
         }
         if isWindowsPlatform():
@@ -199,7 +201,7 @@
                 "Flask", self.tr("Flask"),
                 self.fileTypesCallback,
                 lexerAssociationCallback=self.lexerAssociationCallback,
-#                binaryTranslationsCallback=self.binaryTranslationsCallback,
+                binaryTranslationsCallback=self.binaryTranslationsCallback,
                 progLanguages=self.__supportedVariants[:])
         
         from Project.ProjectBrowser import (
@@ -214,7 +216,7 @@
         
         if self.__e5project.isOpen():
             self.__projectOpened()
-#            self.__object.projectOpenedHooks()
+            self.__object.projectOpenedHooks()
         
         e5App().getObject("Project").projectOpened.connect(
             self.__projectOpened)
@@ -222,13 +224,13 @@
             self.__projectClosed)
         e5App().getObject("Project").newProject.connect(
             self.__projectOpened)
-#        
-#        e5App().getObject("Project").projectOpenedHooks.connect(
-#            self.__object.projectOpenedHooks)
-#        e5App().getObject("Project").projectClosedHooks.connect(
-#            self.__object.projectClosedHooks)
-#        e5App().getObject("Project").newProjectHooks.connect(
-#            self.__object.projectOpenedHooks)
+        
+        e5App().getObject("Project").projectOpenedHooks.connect(
+            self.__object.projectOpenedHooks)
+        e5App().getObject("Project").projectClosedHooks.connect(
+            self.__object.projectClosedHooks)
+        e5App().getObject("Project").newProjectHooks.connect(
+            self.__object.projectOpenedHooks)
         
         return None, True
     
@@ -244,18 +246,18 @@
             self.__projectClosed)
         e5App().getObject("Project").newProject.disconnect(
             self.__projectOpened)
-#        
-#        e5App().getObject("Project").projectOpenedHooks.disconnect(
-#            self.__object.projectOpenedHooks)
-#        e5App().getObject("Project").projectClosedHooks.disconnect(
-#            self.__object.projectClosedHooks)
-#        e5App().getObject("Project").newProjectHooks.disconnect(
-#            self.__object.projectOpenedHooks)
+        
+        e5App().getObject("Project").projectOpenedHooks.disconnect(
+            self.__object.projectOpenedHooks)
+        e5App().getObject("Project").projectClosedHooks.disconnect(
+            self.__object.projectClosedHooks)
+        e5App().getObject("Project").newProjectHooks.disconnect(
+            self.__object.projectOpenedHooks)
         
         self.__e5project.unregisterProjectType("Flask")
         
-#        self.__object.projectClosedHooks()
-#        self.__projectClosed()
+        self.__object.projectClosedHooks()
+        self.__projectClosed()
         
         self.__initialize()
     
@@ -352,6 +354,21 @@
         
         return ""
     
+    def binaryTranslationsCallback(self, filename):
+        """
+        Public method to determine the filename of a compiled translation file
+        given the translation source file.
+        
+        @param filename name of the translation source file
+        @type str
+        @return name of the binary translation file
+        @rtype str
+        """
+        if filename.endswith(".po"):
+            return filename.replace(".po", ".mo")
+        
+        return filename
+    
     def getDefaultPreference(self, key):
         """
         Public method to get the default value for a setting.
diff -r 506c78268b18 -r da6ef8ab8268 ProjectFlask/ConfigurationPage/FlaskPage.py
--- a/ProjectFlask/ConfigurationPage/FlaskPage.py	Sun Nov 15 17:35:48 2020 +0100
+++ b/ProjectFlask/ConfigurationPage/FlaskPage.py	Sun Nov 15 19:53:56 2020 +0100
@@ -10,6 +10,7 @@
 from PyQt5.QtCore import pyqtSlot
 
 from E5Gui.E5Application import e5App
+from E5Gui.E5PathPicker import E5PathPickerModes
 
 from Preferences.ConfigurationPages.ConfigurationPageBase import (
     ConfigurationPageBase
@@ -65,6 +66,9 @@
         self.py3VenvNameComboBox.addItems(
             [""] + sorted(venvManager.getVirtualenvNames()))
         
+        self.translationsEditorPicker.setMode(E5PathPickerModes.OpenFileMode)
+        self.translationsEditorPicker.setFilters(self.tr("All Files (*)"))
+        
         # set initial values
         self.consoleCommandCombo.setEditText(
             self.__plugin.getPreferences("ConsoleCommand"))
@@ -86,6 +90,9 @@
         
         self.urlEdit.setText(
             self.__plugin.getPreferences("FlaskDocUrl"))
+        
+        self.translationsEditorPicker.setText(
+            self.__plugin.getPreferences("TranslationsEditor"))
     
     def save(self):
         """
@@ -107,6 +114,9 @@
         
         self.__plugin.setPreferences(
             "FlaskDocUrl", self.urlEdit.text())
+        
+        self.__plugin.setPreferences(
+            "TranslationsEditor", self.translationsEditorPicker.text())
     
     @pyqtSlot()
     def on_py3VenvNamesReloadButton_clicked(self):
diff -r 506c78268b18 -r da6ef8ab8268 ProjectFlask/ConfigurationPage/FlaskPage.ui
--- a/ProjectFlask/ConfigurationPage/FlaskPage.ui	Sun Nov 15 17:35:48 2020 +0100
+++ b/ProjectFlask/ConfigurationPage/FlaskPage.ui	Sun Nov 15 19:53:56 2020 +0100
@@ -7,10 +7,10 @@
     <x>0</x>
     <y>0</y>
     <width>500</width>
-    <height>508</height>
+    <height>740</height>
    </rect>
   </property>
-  <layout class="QVBoxLayout" name="verticalLayout">
+  <layout class="QVBoxLayout" name="verticalLayout_3">
    <item>
     <widget class="QLabel" name="headerLabel">
      <property name="text">
@@ -175,6 +175,47 @@
     </widget>
    </item>
    <item>
+    <widget class="QGroupBox" name="TranslationsGroup">
+     <property name="title">
+      <string>Translations Editor</string>
+     </property>
+     <layout class="QVBoxLayout" name="verticalLayout">
+      <item>
+       <widget class="QLabel" name="label_10">
+        <property name="minimumSize">
+         <size>
+          <width>0</width>
+          <height>40</height>
+         </size>
+        </property>
+        <property name="text">
+         <string>Enter the path of an editor to use to do the translations. Leave empty to disable this feature.</string>
+        </property>
+        <property name="wordWrap">
+         <bool>true</bool>
+        </property>
+       </widget>
+      </item>
+      <item>
+       <widget class="E5PathPicker" name="translationsEditorPicker" native="true">
+        <property name="sizePolicy">
+         <sizepolicy hsizetype="Expanding" vsizetype="Preferred">
+          <horstretch>0</horstretch>
+          <verstretch>0</verstretch>
+         </sizepolicy>
+        </property>
+        <property name="focusPolicy">
+         <enum>Qt::StrongFocus</enum>
+        </property>
+        <property name="toolTip">
+         <string>Enter the path of the translations editor</string>
+        </property>
+       </widget>
+      </item>
+     </layout>
+    </widget>
+   </item>
+   <item>
     <spacer name="verticalSpacer">
      <property name="orientation">
       <enum>Qt::Vertical</enum>
@@ -189,6 +230,14 @@
    </item>
   </layout>
  </widget>
+ <customwidgets>
+  <customwidget>
+   <class>E5PathPicker</class>
+   <extends>QWidget</extends>
+   <header>E5Gui/E5PathPicker.h</header>
+   <container>1</container>
+  </customwidget>
+ </customwidgets>
  <tabstops>
   <tabstop>consoleCommandCombo</tabstop>
   <tabstop>externalBrowserCheckBox</tabstop>
diff -r 506c78268b18 -r da6ef8ab8268 ProjectFlask/FlaskBabelDetector.py
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ProjectFlask/FlaskBabelDetector.py	Sun Nov 15 19:53:56 2020 +0100
@@ -0,0 +1,19 @@
+# -*- coding: utf-8 -*-
+
+# Copyright (c) 2020 Detlev Offenbach <detlev@die-offenbachs.de>
+#
+
+"""
+Module to check for the presence of 'flask-babel' by importing it.
+"""
+
+import sys
+
+if __name__ == "__main__":
+    try:
+        import flask_babel      # __IGNORE_EXCEPTION__ __IGNORE_WARNING__
+        ret = 0
+    except ImportError:
+        ret = 1
+    
+    sys.exit(ret)
diff -r 506c78268b18 -r da6ef8ab8268 ProjectFlask/FlaskCommandDialog.py
--- a/ProjectFlask/FlaskCommandDialog.py	Sun Nov 15 17:35:48 2020 +0100
+++ b/ProjectFlask/FlaskCommandDialog.py	Sun Nov 15 19:53:56 2020 +0100
@@ -1,5 +1,8 @@
 # -*- coding: utf-8 -*-
 
+# Copyright (c) 2020 Detlev Offenbach <detlev@die-offenbachs.de>
+#
+
 """
 Module implementing a dialog to run a flask command and show its output.
 """
@@ -133,7 +136,7 @@
     @pyqtSlot(QAbstractButton)
     def on_buttonBox_clicked(self, button):
         """
-        Slot handling presses of the button box buttons.
+        Private slot handling presses of the button box buttons.
         
         @param button reference to the button been clicked
         @type QAbstractButton
diff -r 506c78268b18 -r da6ef8ab8268 ProjectFlask/Project.py
--- a/ProjectFlask/Project.py	Sun Nov 15 17:35:48 2020 +0100
+++ b/ProjectFlask/Project.py	Sun Nov 15 19:53:56 2020 +0100
@@ -53,6 +53,7 @@
         self.__virtualEnvManager = e5App().getObject("VirtualEnvManager")
         
         self.__menus = {}   # dictionary with references to menus
+        self.__hooksInstalled = False
          
         self.__serverDialog = None
         self.__routesDialog = None
@@ -264,6 +265,78 @@
         """
         return list(self.__menus.keys())
     
+    def registerOpenHook(self):
+        """
+        Public method to register the open hook to open a translations file
+        in a translations editor.
+        """
+        if self.__hooksInstalled:
+            editor = self.__plugin.getPreferences("TranslationsEditor")
+            if editor:
+                self.__translationsBrowser.addHookMethodAndMenuEntry(
+                    "open", self.openPOEditor,
+                    self.tr("Open with {0}").format(
+                        os.path.basename(editor)))
+            else:
+                self.__translationsBrowser.removeHookMethod("open")
+    
+    def projectOpenedHooks(self):
+        """
+        Public method to add our hook methods.
+        """
+        if self.__e5project.getProjectType() == "Flask":
+##            self.__formsBrowser = (
+##                e5App().getObject("ProjectBrowser")
+##                .getProjectBrowser("forms"))
+##            self.__formsBrowser.addHookMethodAndMenuEntry(
+##                "newForm", self.newForm, self.tr("New template..."))
+##            
+            if self.flaskBabelAvailable():
+                self.__e5project.projectLanguageAddedByCode.connect(
+                    self.__projectLanguageAdded)
+                self.__translationsBrowser = (
+                    e5App().getObject("ProjectBrowser")
+                    .getProjectBrowser("translations"))
+                self.__translationsBrowser.addHookMethodAndMenuEntry(
+                    "extractMessages", self.extractMessages,
+                    self.tr("Extract Messages"))
+                self.__translationsBrowser.addHookMethodAndMenuEntry(
+                    "releaseAll", self.compileCatalogs,
+                    self.tr("Compile All Catalogs"))
+                self.__translationsBrowser.addHookMethodAndMenuEntry(
+                    "releaseSelected", self.compileSelectedCatalogs,
+                    self.tr("Compile Selected Catalogs"))
+                self.__translationsBrowser.addHookMethodAndMenuEntry(
+                    "generateAll", self.updateCatalogs,
+                    self.tr("Update All Catalogs"))
+                self.__translationsBrowser.addHookMethodAndMenuEntry(
+                    "generateSelected", self.updateSelectedCatalogs,
+                    self.tr("Update Selected Catalogs"))
+                
+                self.__hooksInstalled = True
+            
+                self.registerOpenHook()
+    
+    def projectClosedHooks(self):
+        """
+        Public method to remove our hook methods.
+        """
+        if self.__hooksInstalled:
+##            self.__formsBrowser.removeHookMethod("newForm")
+##            self.__formsBrowser = None
+##            
+            self.__e5project.projectLanguageAddedByCode.disconnect(
+                self.__projectLanguageAdded)
+            self.__translationsBrowser.removeHookMethod("extractMessages")
+            self.__translationsBrowser.removeHookMethod("releaseAll")
+            self.__translationsBrowser.removeHookMethod("releaseSelected")
+            self.__translationsBrowser.removeHookMethod("generateAll")
+            self.__translationsBrowser.removeHookMethod("generateSelected")
+            self.__translationsBrowser.removeHookMethod("open")
+            self.__translationsBrowser = None
+        
+        self.__hooksInstalled = False
+    
     ##################################################################
     ## slots below implement general functionality
     ##################################################################
@@ -275,8 +348,6 @@
         for dlg in (self.__serverDialog, self.__routesDialog):
             if dlg is not None:
                 dlg.close()
-##        if self.__serverProc is not None:
-##            self.__serverProcFinished()
     
     def supportedPythonVariants(self):
         """
@@ -583,3 +654,57 @@
         dlg = FlaskCommandDialog(self)
         if dlg.startCommand("init-db"):
             dlg.exec()
+    
+    ##################################################################
+    ## slots and methods below implement i18n and l10n support
+    ##################################################################
+    
+    def flaskBabelAvailable(self):
+        """
+        Public method to check, if the 'flask-babel' package is available.
+        
+        @return flag indicating the availability of 'flask-babel'
+        @rtype bool
+        """
+        venvName = self.__plugin.getPreferences("VirtualEnvironmentNamePy3")
+        interpreter = self.__virtualEnvManager.getVirtualenvInterpreter(
+            venvName)
+        if interpreter and Utilities.isinpath(interpreter):
+            detector = os.path.join(
+                os.path.dirname(__file__), "FlaskBabelDetector.py")
+            proc = QProcess()
+            proc.setProcessChannelMode(QProcess.MergedChannels)
+            proc.start(interpreter, [detector])
+            finished = proc.waitForFinished(30000)
+            if finished and proc.exitCode() == 0:
+                return True
+        
+        return False
+    
+    def __projectLanguageAdded(self, code):
+        # TODO: implement this with pybabel ...
+        pass
+    
+    def openPOEditor(self):
+        # TODO: implement this with pybabel ...
+        pass
+    
+    def extractMessages(self):
+        # TODO: implement this with pybabel ...
+        pass
+    
+    def compileCatalogs(self):
+        # TODO: implement this with pybabel ...
+        pass
+    
+    def compileSelectedCatalogs(self):
+        # TODO: implement this with pybabel ...
+        pass
+    
+    def updateCatalogs(self):
+        # TODO: implement this with pybabel ...
+        pass
+    
+    def updateSelectedCatalogs(self):
+        # TODO: implement this with pybabel ...
+        pass
diff -r 506c78268b18 -r da6ef8ab8268 ProjectFlask/RunServerDialog.py
--- a/ProjectFlask/RunServerDialog.py	Sun Nov 15 17:35:48 2020 +0100
+++ b/ProjectFlask/RunServerDialog.py	Sun Nov 15 19:53:56 2020 +0100
@@ -53,9 +53,9 @@
         self.__process = None
         self.__serverUrl = ""
         
-        self.__ansiRe = re.compile("(\\x1b\[\d+m)")
+        self.__ansiRe = re.compile(r"(\x1b\[\d+m)")
         
-        self.__urlRe = re.compile(r""" * Running on ([^(]+) \(.*""")
+        self.__urlRe = re.compile(r" \* Running on ([^(]+) \(.*")
         
         self.buttonBox.button(QDialogButtonBox.Close).setEnabled(True)
         self.buttonBox.button(QDialogButtonBox.Close).setDefault(True)
@@ -99,13 +99,15 @@
             self.__restartModeAct.setText(
                 self.tr("Re-start Server (Development Mode)"))
     
-    def startServer(self, development=False, restart=False,
-                    askForOptions=False):
+    def startServer(self, development=False, askForOptions=False):
         """
         Public method to start the Flask server process.
         
         @param development flag indicating development mode
         @type bool
+        @param askForOptions flag indicating to ask for server start options
+            first
+        @type bool
         @return flag indicating success
         @rtype bool
         """
@@ -263,6 +265,7 @@
         
         # step 2: start a new server
         self.startServer(development=not self.__serverOptions["development"])
+    
     @pyqtSlot()
     def __restartServerWithOptions(self):
         """
diff -r 506c78268b18 -r da6ef8ab8268 __init__.py
--- a/__init__.py	Sun Nov 15 17:35:48 2020 +0100
+++ b/__init__.py	Sun Nov 15 19:53:56 2020 +0100
@@ -0,0 +1,8 @@
+# -*- coding: utf-8 -*-
+
+# Copyright (c) 2020 Detlev Offenbach <detlev@die-offenbachs.de>
+#
+
+"""
+Package implementing the Flask project plugin.
+"""

eric ide

mercurial