org.pentaho.di.ui.i18n.editor.Translator2.java Source code

Java tutorial

Introduction

Here is the source code for org.pentaho.di.ui.i18n.editor.Translator2.java

Source

/*! ******************************************************************************
 *
 * Pentaho Data Integration
 *
 * Copyright (C) 2002-2013 by Pentaho : http://www.pentaho.com
 *
 *******************************************************************************
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 ******************************************************************************/

package org.pentaho.di.ui.i18n.editor;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

import org.apache.commons.vfs.FileObject;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.SashForm;
import org.eclipse.swt.events.ModifyEvent;
import org.eclipse.swt.events.ModifyListener;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.ShellAdapter;
import org.eclipse.swt.events.ShellEvent;
import org.eclipse.swt.layout.FillLayout;
import org.eclipse.swt.layout.FormAttachment;
import org.eclipse.swt.layout.FormData;
import org.eclipse.swt.layout.FormLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.FileDialog;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.List;
import org.eclipse.swt.widgets.MessageBox;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.TableItem;
import org.eclipse.swt.widgets.Text;
import org.pentaho.di.core.Const;
import org.pentaho.di.core.KettleClientEnvironment;
import org.pentaho.di.core.Props;
import org.pentaho.di.core.exception.KettleException;
import org.pentaho.di.core.exception.KettleFileException;
import org.pentaho.di.core.logging.LogChannel;
import org.pentaho.di.core.logging.LogChannelInterface;
import org.pentaho.di.core.variables.Variables;
import org.pentaho.di.core.vfs.KettleVFS;
import org.pentaho.di.core.xml.XMLHandler;
import org.pentaho.di.i18n.BaseMessages;
import org.pentaho.di.ui.core.PropsUI;
import org.pentaho.di.ui.core.dialog.EnterStringDialog;
import org.pentaho.di.ui.core.dialog.EnterTextDialog;
import org.pentaho.di.ui.core.dialog.ErrorDialog;
import org.pentaho.di.ui.core.gui.GUIResource;
import org.pentaho.di.ui.core.gui.WindowProperty;
import org.pentaho.di.ui.core.widget.ColumnInfo;
import org.pentaho.di.ui.core.widget.TableView;
import org.pentaho.di.ui.i18n.KeyOccurrence;
import org.pentaho.di.ui.i18n.MessagesSourceCrawler;
import org.pentaho.di.ui.i18n.MessagesStore;
import org.pentaho.di.ui.i18n.SourceCrawlerPackageException;
import org.pentaho.di.ui.i18n.SourceCrawlerXMLElement;
import org.pentaho.di.ui.i18n.SourceCrawlerXMLFolder;
import org.pentaho.di.ui.i18n.TranslationsStore;
import org.pentaho.di.ui.trans.step.BaseStepDialog;
import org.w3c.dom.Document;
import org.w3c.dom.Node;

/**
 * Class to allow non-developers to edit translation messages files.
 *
 * @author matt
 *
 */
public class Translator2 {
    /** for i18n purposes, needed by Translator2! */
    private static Class<?> PKG = Translator2.class;

    public static final String APP_NAME = BaseMessages.getString(PKG, "i18nDialog.ApplicationName");

    private Display display;
    private Shell shell;
    private LogChannelInterface log;
    private PropsUI props;

    /** The crawler that can find and contain all the keys in the source code */
    private MessagesSourceCrawler crawler;

    /**
     * The translations store containing all the translations for all keys, locale, packages
     */
    private TranslationsStore store;

    private SashForm sashform;
    private List wLocale;
    private TableView wPackages;
    private List wTodo;

    private String selectedLocale;
    private String selectedMessagesPackage;

    private Text wKey;
    private Text wMain;
    private Text wValue;
    private Text wSource;

    private Button wReload;
    private Button wClose;
    private Button wApply;
    private Button wRevert;
    private Button wSave;
    private Button wZip;

    private Button wSearch;
    private Button wNext;

    private Button wSearchV;
    private Button wNextV;

    /*
     * private Button wSearchG; private Button wNextG;
     */

    private Button wAll;

    private String referenceLocale;
    private java.util.List<String> rootDirectories;
    private java.util.List<String> localeList;
    private java.util.List<String> filesToAvoid;
    protected String lastValue;
    protected boolean lastValueChanged;
    protected String selectedKey;
    protected String searchString;
    protected String lastFoundKey;
    private String singleMessagesFile;
    private String[] scanPhrases;

    private java.util.List<SourceCrawlerXMLFolder> xmlFolders;

    private String selectedSourceFolder;

    public Translator2(Display display) {
        this.display = display;
        this.log = new LogChannel(APP_NAME);
        this.props = PropsUI.getInstance();
    }

    public boolean showKey(String key, String messagesPackage) {
        return !key.startsWith("System.") || messagesPackage.equals(BaseMessages.class.getPackage().getName());
    }

    public void readFiles() throws KettleFileException {
        log.logBasic(BaseMessages.getString(PKG, "i18n.Log.ScanningSourceDirectories"));
        try {
            // crawl through the source directories...
            //
            crawler = new MessagesSourceCrawler(log, rootDirectories, singleMessagesFile, xmlFolders);
            crawler.setScanPhrases(scanPhrases);
            crawler.setFilesToAvoid(filesToAvoid);
            crawler.crawl();

            store = new TranslationsStore(log, localeList, referenceLocale, crawler.getSourcePackageOccurrences());
            store.read(rootDirectories);

            // What are the statistics?
            //
            Map<String, int[]> folderKeyCounts = new HashMap<String, int[]>();
            Map<String, Integer> nrKeysPerFolder = new HashMap<String, Integer>();

            for (String sourceFolder : rootDirectories) {
                folderKeyCounts.put(sourceFolder, new int[localeList.size()]);
            }

            for (String sourceFolder : rootDirectories) {

                int[] keyCounts = folderKeyCounts.get(sourceFolder);
                int nrKeys = 0;

                for (int i = 0; i < localeList.size(); i++) {
                    String locale = localeList.get(i);

                    // Count the number of keys available in that locale...
                    //
                    for (KeyOccurrence keyOccurrence : crawler.getKeyOccurrences(sourceFolder)) {
                        // We don't want the system keys, just the regular ones.
                        //
                        if (showKey(keyOccurrence.getKey(), keyOccurrence.getMessagesPackage())) {
                            String value = store.lookupKeyValue(locale, keyOccurrence.getMessagesPackage(),
                                    keyOccurrence.getKey());
                            if (!Const.isEmpty(value)) {
                                keyCounts[i]++;
                            }
                            if (locale.equals(referenceLocale)) {
                                nrKeys++;
                            }
                        }
                    }
                }
                nrKeysPerFolder.put(sourceFolder, new Integer(nrKeys));
            }

            DecimalFormat pctFormat = new DecimalFormat("#00.00");
            DecimalFormat nrFormat = new DecimalFormat("00");

            for (String sourceFolder : rootDirectories) {
                System.out.println("-------------------------------------");
                System.out.println("Folder: " + sourceFolder);
                System.out.println("-------------------------------------");

                int nrKeys = nrKeysPerFolder.get(sourceFolder);
                System.out.println(BaseMessages.getString(PKG, "i18n.Log.NumberOfKeysFound", "" + nrKeys));

                int[] keyCounts = folderKeyCounts.get(sourceFolder);

                String[] locales = localeList.toArray(new String[localeList.size()]);
                for (int i = 0; i < locales.length; i++) {
                    for (int j = 0; j < locales.length - 1; j++) {
                        if (keyCounts[j] < keyCounts[j + 1]) {
                            int c = keyCounts[j];
                            keyCounts[j] = keyCounts[j + 1];
                            keyCounts[j + 1] = c;

                            String l = locales[j];
                            locales[j] = locales[j + 1];
                            locales[j + 1] = l;
                        }
                    }
                }

                for (int i = 0; i < locales.length; i++) {
                    double donePct = 100 * (double) keyCounts[i] / nrKeys;
                    int missingKeys = nrKeys - keyCounts[i];
                    String statusKeys = "# " + nrFormat.format(i + 1) + " : " + locales[i] + " : "
                            + pctFormat.format(donePct) + "% "
                            + BaseMessages.getString(PKG, "i18n.Log.CompleteKeys", keyCounts[i])
                            + (missingKeys != 0 ? BaseMessages.getString(PKG, "i18n.Log.MissingKeys", missingKeys)
                                    : "");
                    System.out.println(statusKeys);
                }
            }
        } catch (Exception e) {
            throw new KettleFileException(
                    BaseMessages.getString(PKG, "i18n.Log.UnableToGetFiles", rootDirectories.toString()), e);

        }
    }

    public void loadConfiguration(String configFile, String sourceFolder) throws Exception {
        // What are the locale to handle?
        //
        localeList = new ArrayList<String>();
        rootDirectories = new ArrayList<String>();
        filesToAvoid = new ArrayList<String>();
        xmlFolders = new ArrayList<SourceCrawlerXMLFolder>();

        FileObject file = KettleVFS.getFileObject(configFile);
        if (file.exists()) {

            try {
                Document doc = XMLHandler.loadXMLFile(file);
                Node configNode = XMLHandler.getSubNode(doc, "translator-config");

                referenceLocale = XMLHandler.getTagValue(configNode, "reference-locale");

                singleMessagesFile = XMLHandler.getTagValue(configNode, "single-messages-file");

                Node localeListNode = XMLHandler.getSubNode(configNode, "locale-list");
                int nrLocale = XMLHandler.countNodes(localeListNode, "locale");
                if (nrLocale > 0) {
                    localeList.clear();
                }
                for (int i = 0; i < nrLocale; i++) {
                    Node localeNode = XMLHandler.getSubNodeByNr(localeListNode, "locale", i);
                    String locale = XMLHandler.getTagValue(localeNode, "code");
                    localeList.add(locale);
                }

                Node phrasesNode = XMLHandler.getSubNode(configNode, "scan-phrases");
                int nrPhrases = XMLHandler.countNodes(phrasesNode, "scan-phrase");
                scanPhrases = new String[nrPhrases];
                for (int i = 0; i < nrPhrases; i++) {
                    Node phraseNode = XMLHandler.getSubNodeByNr(phrasesNode, "scan-phrase", i);
                    scanPhrases[i] = XMLHandler.getNodeValue(phraseNode);
                }

                Node rootsNode = XMLHandler.getSubNode(configNode, "source-directories");
                int nrRoots = XMLHandler.countNodes(rootsNode, "root");
                if (nrRoots > 0) {
                    rootDirectories.clear();
                }
                for (int i = 0; i < nrRoots; i++) {
                    Node rootNode = XMLHandler.getSubNodeByNr(rootsNode, "root", i);
                    String directory = XMLHandler.getNodeValue(rootNode);
                    directory = sourceFolder + File.separator + directory;
                    rootDirectories.add(directory);
                }

                Node filesNode = XMLHandler.getSubNode(configNode, "files-to-avoid");
                int nrFiles = XMLHandler.countNodes(filesNode, "filename");
                if (nrFiles > 0) {
                    filesToAvoid.clear();
                }
                for (int i = 0; i < nrFiles; i++) {
                    Node fileNode = XMLHandler.getSubNodeByNr(filesNode, "filename", i);
                    String filename = XMLHandler.getNodeValue(fileNode);
                    filesToAvoid.add(filename);
                }

                Node foldersToScanNode = XMLHandler.getSubNode(configNode, "xml-folders-to-scan");
                int nrFolders = XMLHandler.countNodes(foldersToScanNode, "xml-folder-to-scan");
                if (nrFolders > 0) {
                    xmlFolders.clear();
                }
                for (int i = 0; i < nrFolders; i++) {
                    Node folderToScanNode = XMLHandler.getSubNodeByNr(foldersToScanNode, "xml-folder-to-scan", i);
                    String folderName = sourceFolder + File.separator
                            + XMLHandler.getTagValue(folderToScanNode, "folder");
                    String wildcard = XMLHandler.getTagValue(folderToScanNode, "wildcard");
                    String keyPrefix = XMLHandler.getTagValue(folderToScanNode, "key-prefix");
                    SourceCrawlerXMLFolder xmlFolder = new SourceCrawlerXMLFolder(folderName, wildcard, keyPrefix);

                    Node elementsNode = XMLHandler.getSubNode(folderToScanNode, "elements-to-scan");
                    int nrElements = XMLHandler.countNodes(elementsNode, "element-to-scan");
                    for (int j = 0; j < nrElements; j++) {
                        Node elementNode = XMLHandler.getSubNodeByNr(elementsNode, "element-to-scan", j);
                        String element = XMLHandler.getTagValue(elementNode, "element");
                        String tag = XMLHandler.getTagValue(elementNode, "tag");
                        String attribute = XMLHandler.getTagValue(elementNode, "attribute");
                        xmlFolder.getElements().add(new SourceCrawlerXMLElement(element, tag, attribute));
                    }

                    String defaultPackage = XMLHandler.getTagValue(folderToScanNode, "package-default");
                    xmlFolder.setDefaultPackage(defaultPackage);
                    String defaultSourceFolder = XMLHandler.getTagValue(folderToScanNode, "package-source-folder");
                    xmlFolder.setDefaultSourceFolder(sourceFolder + File.separator + defaultSourceFolder);

                    Node packageExceptionsNode = XMLHandler.getSubNode(folderToScanNode, "package-exceptions");
                    int nrExceptions = XMLHandler.countNodes(packageExceptionsNode, "package-exception");
                    for (int j = 0; j < nrExceptions; j++) {
                        Node packageExceptionNode = XMLHandler.getSubNodeByNr(packageExceptionsNode,
                                "package-exception", j);
                        String startsWith = XMLHandler.getTagValue(packageExceptionNode, "starts-with");
                        String packageName = XMLHandler.getTagValue(packageExceptionNode, "package");
                        xmlFolder.getPackageExceptions()
                                .add(new SourceCrawlerPackageException(startsWith, packageName));
                    }

                    xmlFolders.add(xmlFolder);
                }

                System.out.println(BaseMessages.getString(PKG, "i18n.Log.XMLFoldersToScan", xmlFolders.size()));
                for (SourceCrawlerXMLFolder xmlFolder : xmlFolders) {
                    System.out.println(BaseMessages.getString(PKG, "i18n.Log.Folder", xmlFolder.getFolder(),
                            xmlFolder.getWildcard(), xmlFolder.getElements().size()));
                }
            } catch (Exception e) {
                log.logError("Translator", "Error reading translator.xml", e);
            }
        }
    }

    public void open() {
        shell = new Shell(display);
        shell.setLayout(new FillLayout());
        shell.setText(APP_NAME);
        shell.setImage(GUIResource.getInstance().getImageLogoSmall());

        try {
            readFiles();
        } catch (Exception e) {
            new ErrorDialog(shell, "Error reading translations",
                    "There was an unexpected error reading the translations", e);
        }

        // Put something on the screen
        sashform = new SashForm(shell, SWT.HORIZONTAL);
        sashform.setLayout(new FormLayout());

        addLists();
        addGrid();
        addListeners();

        sashform.setWeights(new int[] { 20, 80 });
        sashform.setVisible(true);

        shell.pack();

        refresh();

        shell.setSize(1024, 768);

        shell.open();
    }

    private void addListeners() {
        // In case someone dares to press the [X] in the corner ;-)
        shell.addShellListener(new ShellAdapter() {
            public void shellClosed(ShellEvent e) {
                e.doit = quitFile();
            }
        });

        wReload.addSelectionListener(new SelectionAdapter() {
            public void widgetSelected(SelectionEvent arg0) {
                int[] idx = wPackages.table.getSelectionIndices();
                reload();
                wPackages.table.setSelection(idx);
            }
        });

        wZip.addSelectionListener(new SelectionAdapter() {
            public void widgetSelected(SelectionEvent arg0) {
                saveFilesToZip();
            }
        });

        wClose.addSelectionListener(new SelectionAdapter() {
            public void widgetSelected(SelectionEvent arg0) {
                quitFile();
            }
        });

    }

    public void reload() {
        try {
            shell.setCursor(display.getSystemCursor(SWT.CURSOR_WAIT));
            readFiles();
            shell.setCursor(null);

            refresh();
        } catch (Exception e) {
            new ErrorDialog(shell, "Error loading data", "There was an unexpected error re-loading the data", e);
        }
    }

    public boolean quitFile() {
        java.util.List<MessagesStore> changedMessagesStores = store.getChangedMessagesStores();
        if (changedMessagesStores.size() > 0) {
            MessageBox mb = new MessageBox(shell, SWT.YES | SWT.NO | SWT.ICON_WARNING);
            mb.setMessage(BaseMessages.getString(PKG, "i18nDialog.ChangedFilesWhenExit",
                    changedMessagesStores.size() + ""));
            mb.setText(BaseMessages.getString(PKG, "i18nDialog.Warning"));

            int answer = mb.open();
            if (answer == SWT.NO) {
                return false;
            }
        }

        WindowProperty winprop = new WindowProperty(shell);
        props.setScreen(winprop);
        props.saveProps();

        shell.dispose();
        display.dispose();
        return true;
    }

    private void addLists() {
        Composite composite = new Composite(sashform, SWT.NONE);
        props.setLook(composite);
        FormLayout formLayout = new FormLayout();
        formLayout.marginHeight = Const.FORM_MARGIN;
        formLayout.marginWidth = Const.FORM_MARGIN;
        composite.setLayout(formLayout);

        wLocale = new List(composite, SWT.SINGLE | SWT.BORDER | SWT.V_SCROLL | SWT.H_SCROLL);
        FormData fdLocale = new FormData();
        fdLocale.left = new FormAttachment(0, 0);
        fdLocale.right = new FormAttachment(100, 0);
        fdLocale.top = new FormAttachment(0, 0);
        fdLocale.bottom = new FormAttachment(20, 0);
        wLocale.setLayoutData(fdLocale);

        ColumnInfo[] colinfo = new ColumnInfo[] {
                new ColumnInfo(BaseMessages.getString(PKG, "i18nDialog.SourceFolder"), ColumnInfo.COLUMN_TYPE_TEXT,
                        false, true),
                new ColumnInfo(BaseMessages.getString(PKG, "i18nDialog.Packagename"), ColumnInfo.COLUMN_TYPE_TEXT,
                        false, true), };

        wPackages = new TableView(new Variables(), composite, SWT.FULL_SELECTION | SWT.SINGLE | SWT.BORDER, colinfo,
                1, true, null, props);
        FormData fdPackages = new FormData();
        fdPackages.left = new FormAttachment(0, 0);
        fdPackages.right = new FormAttachment(100, 0);
        fdPackages.top = new FormAttachment(wLocale, Const.MARGIN);
        fdPackages.bottom = new FormAttachment(100, 0);
        wPackages.setLayoutData(fdPackages);
        wPackages.setSortable(false);

        FormData fdComposite = new FormData();
        fdComposite.left = new FormAttachment(0, 0);
        fdComposite.right = new FormAttachment(100, 0);
        fdComposite.top = new FormAttachment(0, 0);
        fdComposite.bottom = new FormAttachment(100, 0);
        composite.setLayoutData(fdComposite);

        // Add a selection listener.
        wLocale.addSelectionListener(new SelectionAdapter() {
            public void widgetSelected(SelectionEvent e) {
                refreshGrid();
            }
        });
        wPackages.table.addSelectionListener(new SelectionAdapter() {
            public void widgetSelected(SelectionEvent e) {
                refreshGrid();
            }
        });

        composite.layout();
    }

    private void addGrid() {
        Composite composite = new Composite(sashform, SWT.NONE);
        props.setLook(composite);

        FormLayout formLayout = new FormLayout();
        formLayout.marginWidth = Const.FORM_MARGIN;
        formLayout.marginHeight = Const.FORM_MARGIN;
        composite.setLayout(formLayout);

        wReload = new Button(composite, SWT.NONE);
        wReload.setText(BaseMessages.getString(PKG, "i18nDialog.Reload"));
        wSave = new Button(composite, SWT.NONE);
        wSave.setText(BaseMessages.getString(PKG, "i18nDialog.Save"));
        wZip = new Button(composite, SWT.NONE);
        wZip.setText(BaseMessages.getString(PKG, "i18nDialog.Zip"));
        wZip.setToolTipText(BaseMessages.getString(PKG, "i18nDialog.Zip.Tip"));
        wClose = new Button(composite, SWT.NONE);
        wClose.setText(BaseMessages.getString(PKG, "i18nDialog.Close"));

        BaseStepDialog.positionBottomButtons(composite, new Button[] { wReload, wSave, wZip, wClose, },
                Const.MARGIN * 3, null);

        /*
         * wSearchG = new Button(composite, SWT.PUSH); wSearchG.setText("   Search &key  "); FormData fdSearchG = new
         * FormData(); fdSearchG.left = new FormAttachment(0, 0); fdSearchG.bottom = new FormAttachment(100, 0);
         * wSearchG.setLayoutData(fdSearchG);
         *
         * wNextG = new Button(composite, SWT.PUSH); wNextG.setText("   Next ke&y  "); FormData fdNextG = new FormData();
         * fdNextG.left = new FormAttachment(wSearchG, Const.MARGIN); fdNextG.bottom = new FormAttachment(100, 0);
         * wNextG.setLayoutData(fdNextG);
         */

        int left = 25;
        int middle = 40;

        wAll = new Button(composite, SWT.CHECK);
        wAll.setText(BaseMessages.getString(PKG, "i18nDialog.ShowAllkeys"));
        props.setLook(wAll);
        FormData fdAll = new FormData();
        fdAll.left = new FormAttachment(0, 0);
        fdAll.right = new FormAttachment(left, 0);
        fdAll.bottom = new FormAttachment(wClose, -Const.MARGIN);
        wAll.setLayoutData(fdAll);

        Label wlTodo = new Label(composite, SWT.LEFT);
        props.setLook(wlTodo);
        wlTodo.setText(BaseMessages.getString(PKG, "i18nDialog.ToDoList"));
        FormData fdlTodo = new FormData();
        fdlTodo.left = new FormAttachment(0, 0);
        fdlTodo.right = new FormAttachment(left, 0);
        fdlTodo.top = new FormAttachment(0, 0);
        wlTodo.setLayoutData(fdlTodo);

        wTodo = new List(composite, SWT.SINGLE | SWT.BORDER | SWT.V_SCROLL | SWT.H_SCROLL);
        FormData fdTodo = new FormData();
        fdTodo.left = new FormAttachment(0, 0);
        fdTodo.right = new FormAttachment(left, 0);
        fdTodo.top = new FormAttachment(wlTodo, Const.MARGIN);
        fdTodo.bottom = new FormAttachment(wAll, -Const.MARGIN);
        wTodo.setLayoutData(fdTodo);

        // The key
        //
        Label wlKey = new Label(composite, SWT.RIGHT);
        wlKey.setText(BaseMessages.getString(PKG, "i18nDialog.TranslationKey"));
        props.setLook(wlKey);
        FormData fdlKey = new FormData();
        fdlKey.left = new FormAttachment(left, Const.MARGIN);
        fdlKey.right = new FormAttachment(middle, 0);
        fdlKey.top = new FormAttachment(wlTodo, Const.MARGIN);
        wlKey.setLayoutData(fdlKey);

        wKey = new Text(composite, SWT.SINGLE | SWT.BORDER | SWT.V_SCROLL | SWT.H_SCROLL);
        props.setLook(wKey);
        FormData fdKey = new FormData();
        fdKey.left = new FormAttachment(middle, Const.MARGIN);
        fdKey.right = new FormAttachment(100, 0);
        fdKey.top = new FormAttachment(wlTodo, Const.MARGIN);
        wKey.setLayoutData(fdKey);
        wKey.setEditable(false);

        // The Main translation
        //
        Label wlMain = new Label(composite, SWT.RIGHT);
        wlMain.setText(BaseMessages.getString(PKG, "i18nDialog.MainTranslation"));
        props.setLook(wlMain);
        FormData fdlMain = new FormData();
        fdlMain.left = new FormAttachment(left, Const.MARGIN);
        fdlMain.right = new FormAttachment(middle, 0);
        fdlMain.top = new FormAttachment(wKey, Const.MARGIN);
        wlMain.setLayoutData(fdlMain);

        wMain = new Text(composite, SWT.MULTI | SWT.BORDER | SWT.V_SCROLL | SWT.H_SCROLL);
        props.setLook(wMain);
        FormData fdMain = new FormData();
        fdMain.left = new FormAttachment(middle, Const.MARGIN);
        fdMain.right = new FormAttachment(100, 0);
        fdMain.top = new FormAttachment(wKey, Const.MARGIN);
        fdMain.bottom = new FormAttachment(wKey, 150 + Const.MARGIN);
        wMain.setLayoutData(fdMain);
        wMain.setEditable(false);

        wSearch = new Button(composite, SWT.PUSH);
        wSearch.setText(BaseMessages.getString(PKG, "i18nDialog.Search"));
        FormData fdSearch = new FormData();
        fdSearch.right = new FormAttachment(middle, -Const.MARGIN * 2);
        fdSearch.top = new FormAttachment(wMain, 0, SWT.CENTER);
        wSearch.setLayoutData(fdSearch);

        wNext = new Button(composite, SWT.PUSH);
        wNext.setText(BaseMessages.getString(PKG, "i18nDialog.Next"));
        FormData fdNext = new FormData();
        fdNext.right = new FormAttachment(middle, -Const.MARGIN * 2);
        fdNext.top = new FormAttachment(wSearch, Const.MARGIN * 2);
        wNext.setLayoutData(fdNext);

        // A few lines of source code at the bottom...
        //
        Label wlSource = new Label(composite, SWT.RIGHT);
        wlSource.setText(BaseMessages.getString(PKG, "i18nDialog.LineOfSourceCode"));
        props.setLook(wlSource);
        FormData fdlSource = new FormData();
        fdlSource.left = new FormAttachment(left, Const.MARGIN);
        fdlSource.right = new FormAttachment(middle, 0);
        fdlSource.top = new FormAttachment(wClose, -100 - Const.MARGIN);
        wlSource.setLayoutData(fdlSource);

        wSource = new Text(composite, SWT.MULTI | SWT.BORDER | SWT.WRAP | SWT.V_SCROLL);
        props.setLook(wSource);
        FormData fdSource = new FormData();
        fdSource.left = new FormAttachment(middle, Const.MARGIN);
        fdSource.right = new FormAttachment(100, 0);
        fdSource.top = new FormAttachment(wClose, -100 - Const.MARGIN);
        fdSource.bottom = new FormAttachment(wClose, -Const.MARGIN);
        wSource.setLayoutData(fdSource);
        wSource.setEditable(false);

        // The translation
        //
        Label wlValue = new Label(composite, SWT.RIGHT);
        wlValue.setText(BaseMessages.getString(PKG, "i18nDialog.Translation"));
        props.setLook(wlValue);
        FormData fdlValue = new FormData();
        fdlValue.left = new FormAttachment(left, Const.MARGIN);
        fdlValue.right = new FormAttachment(middle, 0);
        fdlValue.top = new FormAttachment(wMain, Const.MARGIN);
        wlValue.setLayoutData(fdlValue);

        wValue = new Text(composite, SWT.MULTI | SWT.BORDER | SWT.V_SCROLL | SWT.H_SCROLL);
        props.setLook(wValue);
        FormData fdValue = new FormData();
        fdValue.left = new FormAttachment(middle, Const.MARGIN);
        fdValue.right = new FormAttachment(100, 0);
        fdValue.top = new FormAttachment(wMain, Const.MARGIN);
        fdValue.bottom = new FormAttachment(wSource, -Const.MARGIN);
        wValue.setLayoutData(fdValue);
        wValue.setEditable(true);

        wApply = new Button(composite, SWT.PUSH);
        wApply.setText(BaseMessages.getString(PKG, "i18nDialog.Apply"));
        FormData fdApply = new FormData();
        fdApply.right = new FormAttachment(middle, -Const.MARGIN * 2);
        fdApply.top = new FormAttachment(wValue, 0, SWT.CENTER);
        wApply.setLayoutData(fdApply);
        wApply.setEnabled(false);

        wRevert = new Button(composite, SWT.PUSH);
        wRevert.setText(BaseMessages.getString(PKG, "i18nDialog.Revert"));
        FormData fdRevert = new FormData();
        fdRevert.right = new FormAttachment(middle, -Const.MARGIN * 2);
        fdRevert.top = new FormAttachment(wApply, Const.MARGIN * 2);
        wRevert.setLayoutData(fdRevert);
        wRevert.setEnabled(false);

        wSearchV = new Button(composite, SWT.PUSH);
        wSearchV.setText(BaseMessages.getString(PKG, "i18nDialog.Search"));
        FormData fdSearchV = new FormData();
        fdSearchV.right = new FormAttachment(middle, -Const.MARGIN * 2);
        fdSearchV.top = new FormAttachment(wRevert, Const.MARGIN * 4);
        wSearchV.setLayoutData(fdSearchV);

        wNextV = new Button(composite, SWT.PUSH);
        wNextV.setText(BaseMessages.getString(PKG, "i18nDialog.Next"));
        FormData fdNextV = new FormData();
        fdNextV.right = new FormAttachment(middle, -Const.MARGIN * 2);
        fdNextV.top = new FormAttachment(wSearchV, Const.MARGIN * 2);
        wNextV.setLayoutData(fdNextV);

        wAll.addSelectionListener(new SelectionAdapter() {
            public void widgetSelected(SelectionEvent e) {
                refreshGrid();
            }
        });

        wTodo.addSelectionListener(new SelectionAdapter() {
            public void widgetSelected(SelectionEvent e) {
                // If someone clicks on the todo list, we set the appropriate values
                //
                if (wTodo.getSelectionCount() == 1) {

                    String key = wTodo.getSelection()[0];

                    showKeySelection(key);
                }
            }
        });

        wValue.addModifyListener(new ModifyListener() {
            public void modifyText(ModifyEvent e) {
                // The main value got changed...
                // Capture this automatically
                //
                lastValueChanged = true;
                lastValue = wValue.getText();

                wApply.setEnabled(true);
                wRevert.setEnabled(true);
            }
        });

        wApply.addSelectionListener(new SelectionAdapter() {
            public void widgetSelected(SelectionEvent e) {
                applyChangedValue();
            }
        });

        wRevert.addSelectionListener(new SelectionAdapter() {
            public void widgetSelected(SelectionEvent e) {
                revertChangedValue();
            }
        });

        wSave.addSelectionListener(new SelectionAdapter() {
            public void widgetSelected(SelectionEvent event) {
                saveFiles();
            }
        });

        wSearch.addSelectionListener(new SelectionAdapter() {
            public void widgetSelected(SelectionEvent e) {
                search(referenceLocale);
            }
        });

        wNext.addSelectionListener(new SelectionAdapter() {
            public void widgetSelected(SelectionEvent e) {
                searchAgain(referenceLocale);
            }
        });

        wSearchV.addSelectionListener(new SelectionAdapter() {
            public void widgetSelected(SelectionEvent e) {
                search(selectedLocale);
            }
        });

        wNextV.addSelectionListener(new SelectionAdapter() {
            public void widgetSelected(SelectionEvent e) {
                searchAgain(selectedLocale);
            }
        });

    }

    protected boolean saveFiles() {
        java.util.List<MessagesStore> changedMessagesStores = store.getChangedMessagesStores();
        if (changedMessagesStores.size() > 0) {

            StringBuffer msg = new StringBuffer();
            for (MessagesStore messagesStore : changedMessagesStores) {
                String filename = messagesStore.getSaveFilename(messagesStore.getSourceFolder());
                messagesStore.setFilename(filename);

                msg.append(messagesStore.getFilename());
                if (!new File(filename).exists()) {
                    msg.append(" (NEW!)");
                }
                msg.append(Const.CR);
            }

            EnterTextDialog dialog = new EnterTextDialog(shell,
                    BaseMessages.getString(PKG, "i18nDialog.ChangedFiles"),
                    BaseMessages.getString(PKG, "i18nDialog.ChangedMessagesFiles"), msg.toString());
            if (dialog.open() != null) {
                try {
                    for (MessagesStore messagesStore : changedMessagesStores) {
                        messagesStore.write();
                        log.logBasic(BaseMessages.getString(PKG, "i18n.Log.SavedMessagesFile",
                                messagesStore.getFilename()));
                    }
                } catch (KettleException e) {
                    new ErrorDialog(shell, BaseMessages.getString(PKG, "i18n.UnexpectedError"),
                            "There was an error saving the changed messages files:", e);
                    return false;
                }
                return true;
            } else {
                return false;
            }

        } else {
            // Nothing was saved.
            // TODO: disable the button if nothing changed.
            return true;
        }

    }

    protected void saveFilesToZip() {
        if (saveFiles()) {
            java.util.List<MessagesStore> messagesStores = store.getMessagesStores(selectedLocale, null);
            if (messagesStores.size() > 0) {

                StringBuffer msg = new StringBuffer();
                for (MessagesStore messagesStore : messagesStores) {
                    // Find the main locale variation for this messages store...
                    //
                    MessagesStore mainLocaleMessagesStore = store.findMainLocaleMessagesStore(
                            messagesStore.getSourceFolder(), messagesStore.getMessagesPackage());
                    String sourceDirectory = mainLocaleMessagesStore.getSourceDirectory(rootDirectories);
                    String filename = messagesStore.getSaveFilename(sourceDirectory);
                    messagesStore.setFilename(filename);
                    msg.append(filename).append(Const.CR);
                }

                // Ask for the target filename if we're still here...

                FileDialog dialog = new FileDialog(shell, SWT.OPEN);
                dialog.setFilterExtensions(new String[] { "*.zip", "*" });
                dialog.setFilterNames(new String[] { BaseMessages.getString(PKG, "System.FileType.ZIPFiles"),
                        BaseMessages.getString(PKG, "System.FileType.AllFiles") });
                if (dialog.open() != null) {
                    String zipFilename = dialog.getFilterPath() + System.getProperty("file.separator")
                            + dialog.getFileName();

                    try {
                        ZipOutputStream out = new ZipOutputStream(new FileOutputStream(zipFilename));
                        byte[] buf = new byte[1024];
                        for (MessagesStore messagesStore : messagesStores) {
                            FileInputStream in = new FileInputStream(messagesStore.getFilename());
                            out.putNextEntry(new ZipEntry(messagesStore.getFilename()));
                            int len;
                            while ((len = in.read(buf)) > 0) {
                                out.write(buf, 0, len);
                            }
                            out.closeEntry();
                            in.close();
                        }
                        out.close();
                    } catch (Exception e) {
                        new ErrorDialog(shell, BaseMessages.getString(PKG, "i18n.UnexpectedError"),
                                "There was an error saving the changed messages files:", e);
                    }

                }

            }
        }
    }

    protected void search(String searchLocale) {
        // Ask for the search string...
        //
        EnterStringDialog dialog = new EnterStringDialog(shell, Const.NVL(searchString, ""),
                BaseMessages.getString(PKG, "i18nDialog.SearchKey"),
                BaseMessages.getString(PKG, "i18nDialog.SearchLocale1") + " '" + Const.NVL(searchLocale, "") + "' "
                        + BaseMessages.getString(PKG, "i18nDialog.SearchLocale2"));
        searchString = dialog.open();

        lastFoundKey = null;

        searchAgain(searchLocale);
    }

    protected void searchAgain(String searchLocale) {
        if (searchString != null) {

            // We want to search for key in the list here...
            // That means we'll enter a String to search for in the values
            //

            String upperSearchString = searchString.toUpperCase();

            boolean lastKeyFound = lastFoundKey == null;

            // Search through all the main locale messages stores for the selected
            // package
            //
            java.util.List<MessagesStore> mainLocaleMessagesStores = store.getMessagesStores(searchLocale,
                    selectedMessagesPackage);
            for (MessagesStore messagesStore : mainLocaleMessagesStores) {
                for (String key : messagesStore.getMessagesMap().keySet()) {
                    String value = messagesStore.getMessagesMap().get(key);
                    String upperValue = value.toUpperCase();
                    if (upperValue.indexOf(upperSearchString) >= 0) {
                        // OK, we found a key worthy of our attention...
                        //
                        if (lastKeyFound) {
                            int index = wTodo.indexOf(key);
                            if (index >= 0) {
                                lastFoundKey = key;
                                wTodo.setSelection(index);
                                showKeySelection(wTodo.getSelection()[0]);
                                return;
                            }
                        }
                        if (key.equals(lastFoundKey)) {
                            lastKeyFound = true;
                        }
                    }
                }
            }
        }

    }

    protected void showKeySelection(String key) {
        if (!key.equals(selectedKey)) {

            applyChangedValue();
        }

        if (selectedLocale != null && key != null && selectedMessagesPackage != null) {
            String mainValue = store.lookupKeyValue(referenceLocale, selectedMessagesPackage, key);
            String value = store.lookupKeyValue(selectedLocale, selectedMessagesPackage, key);
            KeyOccurrence keyOccurrence = crawler.getKeyOccurrence(key, selectedMessagesPackage);

            wKey.setText(key);
            wMain.setText(Const.NVL(mainValue, ""));
            wValue.setText(Const.NVL(value, ""));
            wSource.setText(Const.NVL(keyOccurrence.getSourceLine(), ""));

            // Focus on the entry field
            // Put the cursor all the way at the back
            //
            wValue.setFocus();
            wValue.setSelection(wValue.getText().length());
            wValue.showSelection();
            wValue.clearSelection();

            selectedKey = key;
            lastValueChanged = false;
            wApply.setEnabled(false);
            wRevert.setEnabled(false);
        }
    }

    public void refreshGrid() {

        applyChangedValue();

        wTodo.removeAll();
        wKey.setText("");
        wMain.setText("");
        wValue.setText("");
        wSource.setText("");

        selectedLocale = wLocale.getSelectionCount() == 0 ? null : wLocale.getSelection()[0];
        selectedSourceFolder = wPackages.table.getSelectionCount() == 0 ? null
                : wPackages.table.getSelection()[0].getText(1);
        selectedMessagesPackage = wPackages.table.getSelectionCount() == 0 ? null
                : wPackages.table.getSelection()[0].getText(2);
        refreshPackages();

        // Only continue with a locale & a messages package, otherwise we won't
        // budge ;-)
        //
        if (selectedLocale != null && selectedSourceFolder != null && selectedMessagesPackage != null) {
            // Get the list of keys that need a translation...
            //
            java.util.List<KeyOccurrence> todo = getTodoList(selectedLocale, selectedMessagesPackage,
                    selectedSourceFolder, false);
            String[] todoItems = new String[todo.size()];
            for (int i = 0; i < todoItems.length; i++) {
                todoItems[i] = todo.get(i).getKey();
            }
            wTodo.setItems(todoItems);
        }
    }

    private java.util.List<KeyOccurrence> getTodoList(String locale, String messagesPackage, String sourceFolder,
            boolean strict) {
        // Get the list of keys that need a translation...
        //
        java.util.List<KeyOccurrence> keys = crawler.getOccurrencesForPackage(messagesPackage);
        java.util.List<KeyOccurrence> todo = new ArrayList<KeyOccurrence>();
        for (KeyOccurrence keyOccurrence : keys) {
            // Avoid the System keys. Those are taken care off in a different package
            //
            if (showKey(keyOccurrence.getKey(), keyOccurrence.getMessagesPackage())) {
                String value = store.lookupKeyValue(locale, messagesPackage, keyOccurrence.getKey());
                if (Const.isEmpty(value) || (wAll.getSelection() && !strict)) {
                    todo.add(keyOccurrence);
                }
            }
        }

        return todo;
    }

    private void applyChangedValue() {
        // Hang on, before we clear it all, did we have a previous value?
        //
        int todoIndex = wTodo.getSelectionIndex();

        if (selectedKey != null && selectedLocale != null && selectedMessagesPackage != null && lastValueChanged
                && selectedSourceFolder != null) {
            // Store the last modified value
            //
            if (!Const.isEmpty(lastValue)) {
                store.storeValue(selectedLocale, selectedSourceFolder, selectedMessagesPackage, selectedKey,
                        lastValue);
                lastValueChanged = false;

                if (!wAll.getSelection()) {
                    wTodo.remove(selectedKey);
                    if (wTodo.getSelectionIndex() < 0) {
                        // Select the next one in the list...
                        if (todoIndex > wTodo.getItemCount()) {
                            todoIndex = wTodo.getItemCount() - 1;
                        }

                        if (todoIndex >= 0 && todoIndex < wTodo.getItemCount()) {
                            wTodo.setSelection(todoIndex);
                            showKeySelection(wTodo.getSelection()[0]);
                        } else {
                            refreshGrid();
                        }
                    }
                }
            }
            lastValue = null;
            wApply.setEnabled(false);
            wRevert.setEnabled(false);
        }
    }

    private void revertChangedValue() {
        lastValueChanged = false;
        refreshGrid();
    }

    public void refresh() {
        refreshLocale();
        refreshPackages();
        refreshGrid();
    }

    public void refreshPackages() {
        int index = wPackages.getSelectionIndex();

        // OK, we have a distinct list of packages to work with...
        wPackages.table.removeAll();

        Map<String, Map<String, java.util.List<KeyOccurrence>>> sourceMessagesPackages = crawler
                .getSourcePackageOccurrences();

        for (String sourceFolder : sourceMessagesPackages.keySet()) {
            Map<String, java.util.List<KeyOccurrence>> messagesPackages = sourceMessagesPackages.get(sourceFolder);

            for (String messagesPackage : messagesPackages.keySet()) {
                TableItem item = new TableItem(wPackages.table, SWT.NONE);
                item.setText(1, sourceFolder);
                item.setText(2, messagesPackage);

                // count the number of keys for the package that are NOT yet translated...
                //
                if (selectedLocale != null) {
                    java.util.List<KeyOccurrence> todo = getTodoList(selectedLocale, messagesPackage, sourceFolder,
                            true);
                    if (todo.size() > 50) {
                        item.setBackground(GUIResource.getInstance().getColorRed());
                    } else if (todo.size() > 25) {
                        item.setBackground(GUIResource.getInstance().getColorOrange());
                    } else if (todo.size() > 10) {
                        item.setBackground(GUIResource.getInstance().getColorYellow());
                    } else if (todo.size() > 5) {
                        item.setBackground(GUIResource.getInstance().getColorBlue());
                    } else if (todo.size() > 0) {
                        item.setBackground(GUIResource.getInstance().getColorGreen());
                    }
                }
            }
            if (messagesPackages.size() == 0) {
                new TableItem(wPackages.table, SWT.NONE);
            } else {
                wPackages.setRowNums();
                wPackages.optWidth(true);
            }
        }

        if (index >= 0) {
            wPackages.table.setSelection(index);
            wPackages.table.showSelection();
        }
    }

    public void refreshLocale() {
        // OK, we have a distinct list of locale to work with...
        wLocale.removeAll();
        wLocale.setItems(localeList.toArray(new String[localeList.size()]));
    }

    public String toString() {
        return APP_NAME;
    }

    public static void main(String[] args) throws Exception {

        if (args.length != 2) {
            System.err.println("Usage: Translator <translator.xml> <path-to-source>");
            System.err.println("Example:");
            System.err.println("sh translator.sh translator.xml .");
            System.exit(1);
        }

        KettleClientEnvironment.init();

        String configFile = args[0];
        String sourceFolder = args[1];

        Display display = new Display();
        LogChannelInterface log = new LogChannel(APP_NAME);
        PropsUI.init(display, Props.TYPE_PROPERTIES_SPOON);

        Translator2 translator = new Translator2(display);
        translator.loadConfiguration(configFile, sourceFolder);
        translator.open();

        try {
            while (!display.isDisposed()) {
                if (!display.readAndDispatch()) {
                    display.sleep();
                }
            }
        } catch (Throwable e) {
            log.logError(BaseMessages.getString(PKG, "i18n.UnexpectedError", e.getMessage()));
            log.logError(Const.getStackTracker(e));
        }
    }

}