/*! ****************************************************************************** * * Pentaho Data Integration * * Copyright (C) 2002-2016 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.vfs2.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.util.Utils; 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 ( !Utils.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[] { 40, 60 } ); 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( 10, 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 ) { StringBuilder msg = new StringBuilder(); 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 ) { StringBuilder msg = new StringBuilder(); 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 ( Utils.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 ( !Utils.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 ); wPackages.getTable().getColumn( 1 ).setWidth( 100 ); } } 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 ) ); } } }