Package net.sourceforge.squirrel_sql.plugins.i18n

Source Code of net.sourceforge.squirrel_sql.plugins.i18n.TranslatorsController

package net.sourceforge.squirrel_sql.plugins.i18n;

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Locale;
import java.util.prefs.Preferences;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;

import javax.swing.JFileChooser;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JPopupMenu;

import net.sourceforge.squirrel_sql.client.IApplication;
import net.sourceforge.squirrel_sql.client.plugin.PluginInfo;
import net.sourceforge.squirrel_sql.client.util.ApplicationFiles;
import net.sourceforge.squirrel_sql.fw.util.LocaleUtils;
import net.sourceforge.squirrel_sql.fw.util.StringManager;
import net.sourceforge.squirrel_sql.fw.util.StringManagerFactory;
import net.sourceforge.squirrel_sql.fw.util.StringUtilities;
import net.sourceforge.squirrel_sql.fw.util.log.ILogger;
import net.sourceforge.squirrel_sql.fw.util.log.LoggerController;

public class TranslatorsController
{
  private static final StringManager s_stringMgr =
    StringManagerFactory.getStringManager(TranslatorsController.class);

  private static ILogger logger = LoggerController.createLogger(TranslatorsController.class);

  private static final String PREF_KEY_WORK_DIR = "SquirrelSQL.i18n.workDir";

  private static final String PREF_KEY_EDITOR_COMMAND = "SquirrelSQL.i18n.editorCommand";

  private static final String PREF_KEY_SELECTED_LOCALE = "SquirrelSQL.i18n.selectedLocale";

  private static final String PREF_KEY_NATIVE2ASCII_COMMAND = "SquirrelSQL.i18n.native2AsciiCommand";

  private static final String PREF_KEY_NATIVE2ASCII_OUT_DIR = "SquirrelSQL.i18n.native2AsciiOutDir";

  private static final String PREF_KEY_INCLUDE_TIMESTAMP = "SquirrelSQL.i18n.includeTimestamp";

  TranslatorsPanel _panel;

  private IApplication _app;

  private BundlesTableModel _bundlesTableModel;

  private JPopupMenu _popUp = new JPopupMenu();

  // i18n[I18n.generateTemplateComments=Generate template comments for missing translations]
  private JMenuItem _mnuGenerateTemplateComments =
    new JMenuItem(s_stringMgr.getString("I18n.generateTemplateComments"));

  // i18n[I18n.openIOnEditor=Open in Editor]
  private JMenuItem _mnuOpenInEditor = new JMenuItem(s_stringMgr.getString("I18n.openIOnEditor"));

  // i18n[I18n.ExecuteNativeToAscii=Execute nativeToAscii]
  private JMenuItem _mnuExecuteNativeToAscii =
    new JMenuItem(s_stringMgr.getString("I18n.ExecuteNativeToAscii"));

  TranslatorsController(TranslatorsPanel panel)
  {
    _panel = panel;

    _bundlesTableModel = new BundlesTableModel();

    _panel.tblBundels.setModel(_bundlesTableModel);

    _panel.cbxIncludeTimestamp.addActionListener(new ActionListener()
    {
      public void actionPerformed(ActionEvent e)
      {
        String includeTimestamp = Boolean.valueOf(_panel.cbxIncludeTimestamp.isSelected()).toString();
        Preferences.userRoot().put(PREF_KEY_INCLUDE_TIMESTAMP, includeTimestamp);
      }
    });

    _panel.cbxExcludeComplete.addActionListener(new ActionListener()
    {
      public void actionPerformed(ActionEvent e)
      {
        onLoadBundels(_app);
        String excludeComplete = Boolean.valueOf(_panel.cbxExcludeComplete.isSelected()).toString();
        Preferences.userRoot().put(TranslatorsPanel.PREF_KEY_EXCLUDE_COMPLETE, excludeComplete);
      }
    });

    _panel.btnChooseWorkDir.addActionListener(new ActionListener()
    {
      public void actionPerformed(ActionEvent e)
      {
        onChooseWorkDir();
      }
    });

    _panel.cboLocales.addItemListener(new ItemListener()
    {
      public void itemStateChanged(ItemEvent e)
      {
        onLocaleChanged(e);
      }
    });

    _panel.btnChooseEditorCommand.addActionListener(new ActionListener()
    {
      public void actionPerformed(ActionEvent e)
      {
        onChooseEditorCommand();
      }
    });

    _panel.btnChooseNativeToAsciiCommand.addActionListener(new ActionListener()
    {
      public void actionPerformed(ActionEvent e)
      {
        onChooseNativeToAsciiCommand();
      }
    });

    _panel.btnChooseNativeToAsciiOutDir.addActionListener(new ActionListener()
    {
      public void actionPerformed(ActionEvent e)
      {
        onChooseNativeToAsciiOutDir();
      }
    });

    _panel.tblBundels.addMouseListener(new MouseAdapter()
    {
      public void mousePressed(MouseEvent e)
      {
        maybeShowPopup(e);
      }

      public void mouseReleased(MouseEvent e)
      {
        maybeShowPopup(e);
      }
    });

    _popUp.add(_mnuGenerateTemplateComments);
    _popUp.add(_mnuOpenInEditor);
    _popUp.add(_mnuExecuteNativeToAscii);

    _mnuGenerateTemplateComments.addActionListener(new ActionListener()
    {
      public void actionPerformed(ActionEvent e)
      {
        onGenerate();
      }
    });

    _mnuOpenInEditor.addActionListener(new ActionListener()
    {
      public void actionPerformed(ActionEvent e)
      {
        onOpenInEditor();
      }
    });

    _mnuExecuteNativeToAscii.addActionListener(new ActionListener()
    {
      public void actionPerformed(ActionEvent e)
      {
        onExecuteNativeToAscii();
      }
    });

    Locale[] availableLocales = LocaleUtils.getAvailableLocales();

    Locale selectedLocale = Locale.getDefault();
    String prefLocale = Preferences.userRoot().get(PREF_KEY_SELECTED_LOCALE, null);

    for (int i = 0; i < availableLocales.length; i++)
    {
      _panel.cboLocales.addItem(availableLocales[i]);

      if (availableLocales[i].toString().equals(prefLocale))
      {
        selectedLocale = availableLocales[i];
      }
    }
    _panel.cboLocales.setSelectedItem(selectedLocale);

    _panel.btnLoad.addActionListener(new ActionListener()
    {
      public void actionPerformed(ActionEvent e)
      {
        onLoadBundels(_app);
      }
    });

    String workDir = Preferences.userRoot().get(PREF_KEY_WORK_DIR, null);
    _panel.txtWorkingDir.setText(workDir);

    String editorCommand = Preferences.userRoot().get(PREF_KEY_EDITOR_COMMAND, null);
    _panel.txtEditorCommand.setText(editorCommand);

    String nativeToAsciiCommand = Preferences.userRoot().get(PREF_KEY_NATIVE2ASCII_COMMAND, null);
    _panel.txtNativeToAsciiCommand.setText(nativeToAsciiCommand);

    String nativeToAsciiOutDir = Preferences.userRoot().get(PREF_KEY_NATIVE2ASCII_OUT_DIR, null);
    _panel.txtNativeToAsciiOutDir.setText(nativeToAsciiOutDir);

  }

  private void onChooseWorkDir()
  {
    JFileChooser chooser = new JFileChooser(System.getProperties().getProperty("user.home"));
    chooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
    chooser.showOpenDialog(_app.getMainFrame());

    if (null != chooser.getSelectedFile())
    {
      _panel.txtWorkingDir.setText(chooser.getSelectedFile().getPath());
    }
  }

  private void onLocaleChanged(ItemEvent e)
  {
    if (ItemEvent.SELECTED == e.getStateChange())
    {
      _bundlesTableModel.setBundles(new I18nBundle[0]);
    }
  }

  private void onChooseEditorCommand()
  {
    JFileChooser chooser = new JFileChooser(System.getProperties().getProperty("user.home"));
    chooser.setFileSelectionMode(JFileChooser.FILES_ONLY);
    chooser.showOpenDialog(_app.getMainFrame());

    if (null != chooser.getSelectedFile())
    {
      _panel.txtEditorCommand.setText(chooser.getSelectedFile().getPath());
    }
  }

  private void onChooseNativeToAsciiCommand()
  {
    JFileChooser chooser = new JFileChooser(System.getProperties().getProperty("user.home"));
    chooser.setFileSelectionMode(JFileChooser.FILES_ONLY);
    chooser.showOpenDialog(_app.getMainFrame());

    if (null != chooser.getSelectedFile())
    {
      _panel.txtNativeToAsciiCommand.setText(chooser.getSelectedFile().getPath());
    }
  }

  private void onChooseNativeToAsciiOutDir()
  {
    JFileChooser chooser = new JFileChooser(System.getProperties().getProperty("user.home"));
    chooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
    chooser.showOpenDialog(_app.getMainFrame());

    if (null != chooser.getSelectedFile())
    {
      _panel.txtNativeToAsciiOutDir.setText(chooser.getSelectedFile().getPath());
    }
  }

  private void onExecuteNativeToAscii()
  {
    String nativeToAsciiCommmand = _panel.txtNativeToAsciiCommand.getText();

    if (null == nativeToAsciiCommmand || 0 == nativeToAsciiCommmand.length())
    {
      // i18n[i18n.noNativeToAsciiCommand=Cannot convert files without a native2Ascii command.]
      String msg = s_stringMgr.getString("i18n.noNativeToAsciiCommand");
      JOptionPane.showMessageDialog(_app.getMainFrame(), msg);
      return;
    }

    String nativeToAsciiOutDir = _panel.txtNativeToAsciiOutDir.getText();

    if (null == nativeToAsciiOutDir || 0 == nativeToAsciiOutDir.length())
    {
      // i18n[i18n.noNativeToAsciiOutDir=Cannot convert files without a native2Ascii output dir.]
      String msg = s_stringMgr.getString("i18n.noNativeToAsciiOutDir");
      JOptionPane.showMessageDialog(_app.getMainFrame(), msg);
      return;
    }

    File outDir = new File(nativeToAsciiOutDir);
    outDir.mkdirs();

    if (false == outDir.isDirectory())
    {
      // i18n[i18n.noNativeToAsciiOutDirNoDir=native2Ascii output dir is not a directory. native2Ascii will
      // not be executed.]
      String msg = s_stringMgr.getString("i18n.noNativeToAsciiOutDirNoDir");
      JOptionPane.showMessageDialog(_app.getMainFrame(), msg);
      return;
    }

    executeCommand(nativeToAsciiCommmand, true, outDir);
  }

  private void onOpenInEditor()
  {

    String editorCommmand = _panel.txtEditorCommand.getText();

    if (null == editorCommmand || 0 == editorCommmand.length())
    {
      String msg = s_stringMgr.getString("i18n.noEditorCommand");
      // i18n[i18n.noEditorCommand=Can not open files withou an editor command.]
      JOptionPane.showMessageDialog(_app.getMainFrame(), msg);
      return;
    }

    executeCommand(editorCommmand, false, null);
  }

  private void executeCommand(String command, boolean nativeToAscii, File native2AsciiOutDir)
  {
    File workDir = getWorkDir(true);

    int[] selRows = _panel.tblBundels.getSelectedRows();
    I18nBundle[] selBundles = _bundlesTableModel.getBundlesForRows(selRows);

    if (0 == selBundles.length)
    {
      return;
    }

    boolean filesFound = false;

    ArrayList<String> commands = new ArrayList<String>();
    for (int i = 0; i < selBundles.length; i++)
    {
      File f = selBundles[i].getPathRelativeTo(workDir);

      if (f.exists())
      {
        if (nativeToAscii)
        {
          File outFile = selBundles[i].getPathRelativeTo(native2AsciiOutDir);
          outFile.getParentFile().mkdirs();
          commands.add(command + " " + f.getPath() + " " + outFile.getPath());
        }
        else
        {
          if (0 == commands.size())
          {
            commands.add(command);
          }
          commands.add(commands.remove(0) + " " + f.getPath());
        }

        filesFound = true;
      }
      else
      {
        // i18n[i18n.notGeneratedInWorkDir=File {0} has not been generated. Cannot continue.]
        String msg = s_stringMgr.getString("i18n.notGeneratedInWorkDir", f.getPath());
        _app.getMessageHandler().showMessage(msg);
      }
    }

    if (false == filesFound)
    {
      // i18n[i18n.noFilesOpened=No file found.\nSee message panel for details.]
      String msg = s_stringMgr.getString("i18n.noFilesOpened");
      JOptionPane.showMessageDialog(_app.getMainFrame(), msg);
      return;
    }

    try
    {
      for (int i = 0; i < commands.size(); i++)
      {
        // i18n[i18n.executingCommand=Executing command: {0}]
        String msg = s_stringMgr.getString("i18n.executingCommand", commands.get(i));
        _app.getMessageHandler().showMessage(msg);

        Runtime.getRuntime().exec(commands.get(i));
      }
    }
    catch (IOException e)
    {
      // i18n[i18n.executingCommandFailed=Execution failed with error: {0}]
      String msg = s_stringMgr.getString("i18n.executingCommandFailed", e.getMessage());
      _app.getMessageHandler().showMessage(msg);
      throw new RuntimeException(e);

    }
  }

  public void uninitialize()
  {
    Preferences.userRoot().put(PREF_KEY_SELECTED_LOCALE, "" + _panel.cboLocales.getSelectedItem());
    Preferences.userRoot().put(PREF_KEY_WORK_DIR, _panel.txtWorkingDir.getText());
    Preferences.userRoot().put(PREF_KEY_EDITOR_COMMAND, _panel.txtEditorCommand.getText());
    Preferences.userRoot().put(PREF_KEY_NATIVE2ASCII_COMMAND, _panel.txtNativeToAsciiCommand.getText());
    Preferences.userRoot().put(PREF_KEY_NATIVE2ASCII_OUT_DIR, _panel.txtNativeToAsciiOutDir.getText());
  }

  private void maybeShowPopup(MouseEvent e)
  {
    if (e.isPopupTrigger())
    {
      _popUp.show(e.getComponent(), e.getX(), e.getY());
    }
  }

  private void onGenerate()
  {
    File workDir = getWorkDir(true);

    if (null == workDir)
    {
      return;
    }

    int[] selRows = _panel.tblBundels.getSelectedRows();
    I18nBundle[] selBundles = _bundlesTableModel.getBundlesForRows(selRows);

    for (int i = 0; i < selBundles.length; i++)
    {
      selBundles[i].writeMissingProps(_app, workDir);
    }
  }

  /**
   * Checks if the workdir is a valid directory name and create the directory if it doesn't exist.
   *
   * @return null if to directory name is not valid
   */
  private File getWorkDir(boolean withMessages)
  {
    String buf = _panel.txtWorkingDir.getText();
    if (null == buf || 0 == buf.trim().length())
    {

      if (withMessages)
      {
        String msg = s_stringMgr.getString("I18n.NoWorkDir");
        // i18n[I18n.NoWorkDir=Please choose a work dir to store your translations.]
        JOptionPane.showMessageDialog(_app.getMainFrame(), msg);
      }
      return null;

    }

    File workDir = new File(buf);
    if (false == workDir.isDirectory())
    {
      if (withMessages)
      {
        String msg = s_stringMgr.getString("I18n.WorkDirIsNotADirectory", workDir.getPath());
        // i18n[I18n.WorkDirIsNotADirectory=Working directory {0} is not a directory]
        JOptionPane.showMessageDialog(_app.getMainFrame(), msg);
      }
      return null;
    }

    if (false == workDir.exists())
    {
      String msg = s_stringMgr.getString("I18n.WorkDirDoesNotExistQuestionCreate", workDir.getPath());
      // i18n[I18n.WorkDirDoesNotExistQuestionCreate=Working directory {0} does not exist.\nDo you want to
      // create it?]

      if (withMessages)
      {
        if (JOptionPane.YES_OPTION == JOptionPane.showConfirmDialog(_app.getMainFrame(), msg))
        {
          if (false == workDir.mkdirs())
          {
            msg = s_stringMgr.getString("I18n.CouldNotCreateWorkDir", workDir.getPath());
            // i18n[I18n.CouldNotCreateWorkDir=Could not create Working directory {0}]
            JOptionPane.showMessageDialog(_app.getMainFrame(), msg);
            return null;

          }
        }
      }
      else
      {
        return null;
      }
    }

    return workDir;

  }

  private void onLoadBundels(IApplication app)
  {
    Locale selLocale = (Locale) _panel.cboLocales.getSelectedItem();

    File workDir = null;
    if (null != _panel.txtWorkingDir.getText() && 0 < _panel.txtWorkingDir.getText().trim().length())
    {
      workDir = new File(_panel.txtWorkingDir.getText());
      if (false == workDir.exists())
      {
        String msg = s_stringMgr.getString("I18n.noWorkdir");
        // i18n[I18n.noWorkdir=Working directory doesn't exist.\nDo you want to create it?]
        if (JOptionPane.YES_OPTION == JOptionPane.showConfirmDialog(app.getMainFrame(), msg))
        {
          workDir.mkdirs();
        }
      }
      else if (false == workDir.isDirectory())
      {
        String msg = s_stringMgr.getString("I18n.WorkdirIsNoDir", workDir.getAbsolutePath());
        // i18n[I18n.WorkdirIsNoDir=The working directory is not a directory.\nNo bundles will be loaded
        // from {0}]
        JOptionPane.showMessageDialog(app.getMainFrame(), msg);

      }
    }
    else
    {
      String msg = s_stringMgr.getString("I18n.noWorkdirSpecified");
      // i18n[I18n.noWorkdirSpecified=No working directory specified.]
      JOptionPane.showMessageDialog(app.getMainFrame(), msg);
    }

    URL[] sourceUrls = getUrlsToLoadI18nPropertiesFrom(workDir);

    String pluginDir = new ApplicationFiles().getPluginsDirectory().getPath();

    ArrayList<I18nProps> defaultI18nProps = new ArrayList<I18nProps>();
    ArrayList<I18nProps> localizedI18nProps = new ArrayList<I18nProps>();

    for (int i = 0; i < sourceUrls.length; i++)
    {
      File file = new File(sourceUrls[i].getFile().replaceAll("%20", " "));

      if (file.isDirectory())
      {
        findI18nInDir(selLocale, file, defaultI18nProps, localizedI18nProps, sourceUrls);
      }
      else if (file.getName().equalsIgnoreCase("squirrel-sql.jar")
        || file.getName().equalsIgnoreCase("fw.jar"))
      {
        findI18nInArchive(selLocale, file, defaultI18nProps, localizedI18nProps, sourceUrls);
      }
      else if (file.getPath().startsWith(pluginDir))
      {
        findI18nInArchive(selLocale, file, defaultI18nProps, localizedI18nProps, sourceUrls);
      }
    }

    Hashtable<String, I18nBundle> i18nBundlesByName = new Hashtable<String, I18nBundle>();

    for (int i = 0; i < defaultI18nProps.size(); i++)
    {
      I18nProps i18nProps = defaultI18nProps.get(i);
      I18nBundle pack = new I18nBundle(i18nProps, selLocale, getWorkDir(false), sourceUrls);
      if (!_panel.cbxExcludeComplete.isSelected() || pack.getMissingTranslationsCount()!=0) {
        i18nBundlesByName.put(i18nProps.getPath(), pack);
      }
    }

    for (int i = 0; i < localizedI18nProps.size(); i++)
    {
      I18nProps locI18nProps = localizedI18nProps.get(i);
      String key = locI18nProps.getUnlocalizedPath(selLocale);

      I18nBundle bundle = i18nBundlesByName.get(key);
      if (null != bundle)
      {
        bundle.setLocalizedProp(locI18nProps);
      }
    }

    I18nBundle[] bundles = i18nBundlesByName.values().toArray(new I18nBundle[0]);

    int[] selRows = _panel.tblBundels.getSelectedRows();
    _bundlesTableModel.setBundles(bundles);

    for (int i = 0; i < selRows.length; i++)
    {
      _panel.tblBundels.getSelectionModel().addSelectionInterval(selRows[i], selRows[i]);
    }

  }

  private URL[] getUrlsToLoadI18nPropertiesFrom(File workDir)
  {
    ApplicationFiles af = new ApplicationFiles();

    ArrayList<URL> ret = new ArrayList<URL>();
    URL[] urls;

    urls = ((URLClassLoader) _app.getClass().getClassLoader()).getURLs();

    for (int i = 0; i < urls.length; i++)
    {
      File file = new File(urls[i].getFile().replaceAll("%20", " "));
      if (file.getName().equals(af.getSQuirrelJarFile().getName()))
      {
        ret.add(urls[i]);
      }
      else if (file.getName().equals(af.getFwJarFile().getName()))
      {
        ret.add(urls[i]);
      }
    }

    PluginInfo[] pi = _app.getPluginManager().getPluginInformation();

    urls = _app.getPluginManager().getPluginURLs();
    for (int i = 0; i < urls.length; i++)
    {
      String jarName = new File(urls[i].getFile()).getName();

      String cleanJarName;
      if (jarName.endsWith(".jar"))
      {
        cleanJarName = jarName.substring(0, jarName.length() - ".jar".length());
      }
      else
      {
        continue;
      }

      for (int j = 0; j < pi.length; j++)
      {
        if (pi[j].getInternalName().equalsIgnoreCase(cleanJarName))
        {
          ret.add(urls[i]);
        }
      }
    }

    return ret.toArray(new URL[ret.size()]);
  }

  private void findI18nInArchive(Locale selLoc, File file, ArrayList<I18nProps> defaultI18nProps,
    ArrayList<I18nProps> localizedI18nProps, URL[] sourceUrls)
  {
    try
    {
      ZipFile zf = new ZipFile(file);

      for (Enumeration<? extends ZipEntry> e = zf.entries(); e.hasMoreElements();)
      {
        ZipEntry entry = e.nextElement();

        if (isTranslationPropertyFile(entry.getName()))
        {
          Locale loc = I18nProps.parseLocaleFromPropsFileName(entry.getName());

          if (null == loc)
          {
            defaultI18nProps.add(new I18nProps(file, entry.getName(), sourceUrls));
          }
          if (selLoc.equals(loc))
          {
            localizedI18nProps.add(new I18nProps(file, entry.getName(), sourceUrls));
          }
        }
      }

    }
    catch (IOException e)
    {
      // i18n[I18n.failedToOpenZip=Failed to open zip/jar {0}]
      String msg = s_stringMgr.getString("I18n.failedToOpenZip", file.getAbsolutePath());
      _app.getMessageHandler().showMessage(msg);
      logger.error(msg, e);
    }

  }

  private void findI18nInDir(Locale selLoc, File dir, ArrayList<I18nProps> defaultI18nProps,
    ArrayList<I18nProps> localizedI18nProps, URL[] sourceUrls)
  {
    File[] files = dir.listFiles();

    for (int i = 0; i < files.length; i++)
    {
      if (files[i].isDirectory())
      {
        findI18nInDir(selLoc, files[i], defaultI18nProps, localizedI18nProps, sourceUrls);
      }
      else if (isTranslationPropertyFile(files[i].getName()))
      {
        Locale loc = I18nProps.parseLocaleFromPropsFileName(files[i].getName());

        if (null == loc)
        {
          defaultI18nProps.add(new I18nProps(files[i], sourceUrls));
        }
        if (selLoc.equals(loc))
        {
          localizedI18nProps.add(new I18nProps(files[i], sourceUrls));
        }
      }
    }

  }

  /**
   * Returns a boolean value indicating whether or not the specified filename is a translation properties
   * file.
   *
   * @param filename
   *           the filename to examine
   * @return true if filename is a translation properties file; false otherwise.
   */
  private boolean isTranslationPropertyFile(String filename)
  {
    if (StringUtilities.isEmpty(filename))
    {
      return false;
    }
    if (filename.endsWith(".properties") && filename.toLowerCase().contains("i18nstrings"))
    {
      return true;
    }
    if (logger.isDebugEnabled()) {
      logger.debug("File ("+filename+" doesn't appear to be a translation properties file.");
    }
    return false;
  }

  public void initialize(IApplication app)
  {
    _app = app;
  }

}
TOP

Related Classes of net.sourceforge.squirrel_sql.plugins.i18n.TranslatorsController

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.