/*! ******************************************************************************
*
* 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.util.ArrayList;
import java.util.Collections;
import java.util.Hashtable;
import java.util.Properties;
import java.util.Set;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.SashForm;
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.graphics.Color;
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.List;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.TableColumn;
import org.eclipse.swt.widgets.TableItem;
import org.pentaho.di.core.Const;
import org.pentaho.di.core.util.Utils;
import org.pentaho.di.core.Props;
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.ui.core.PropsUI;
import org.pentaho.di.ui.core.dialog.EnterListDialog;
import org.pentaho.di.ui.core.dialog.ErrorDialog;
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.trans.step.BaseStepDialog;
public class Translator {
public static final String APP_NAME = "Pentaho Translator";
public static final String ROOT = "src/be/ibridge/kettle";
public static final String EXTENSION = ".properties";
public static final String MESSAGES_DIR = "messages";
public static final String MESSAGES_PREFIX = "messages";
public static final int LOCALE_LENGTH = 5;
public static final String SYSTEM_KEY_PREFIX = "System";
public static final String REFERENCE_LOCALE = "en_US";
private Display display;
private Shell shell;
private LogChannelInterface log;
private PropsUI props;
private Color unusedColor;
// All the properties files found...
Hashtable<String, Properties> files;
private SashForm sashform;
private List wList;
private TableView wGrid;
private Hashtable<String, Integer> directories;
private Hashtable<String, Boolean> locales;
private Hashtable<String, String> javaFiles;
private Button wReload;
private Button wClose;
private Button wLocale;
private Button wVerify;
private Button wUsed;
private Button wAvailable;
public Translator( Display display ) {
this.display = display;
this.log = new LogChannel( APP_NAME );
this.props = PropsUI.getInstance();
clear();
unusedColor = display.getSystemColor( SWT.COLOR_YELLOW );
}
private void clear() {
files = new Hashtable<String, Properties>( 500 );
directories = new Hashtable<String, Integer>( 100 );
javaFiles = new Hashtable<String, String>( 500 );
locales = new Hashtable<String, Boolean>( 20 );
}
public void readFiles( String directory ) throws KettleFileException {
log.logBasic( "Scanning directory: " + directory );
try {
File file = new File( directory );
File[] entries = file.listFiles();
for ( int i = 0; i < entries.length; i++ ) {
File entry = entries[i];
if ( entry.isDirectory() ) {
if ( !entry.getName().startsWith( ".svn" ) ) {
readFiles( directory + "/" + entry.getName() );
}
} else {
if ( entry.isFile() ) {
if ( entry.getName().endsWith( ".properties" ) ) { // Load this one!
String filename = directory + "/" + entry.getName();
log.logBasic( "Reading properties file: " + filename + " (" + entry.getAbsolutePath() + ")" );
Properties properties = new Properties();
properties.load( new FileInputStream( entry ) );
// Store it in the map:
files.put( filename, properties );
}
}
}
}
// get the list of distinct directories:
// at the same time, keep a list of available locales
//
directories = new Hashtable<String, Integer>( files.size() );
locales = new Hashtable<String, Boolean>( 10 );
for ( String filename : files.keySet() ) {
String path = getPath( filename );
// is it already in there?
Integer num = directories.get( path );
if ( num != null ) {
num = Integer.valueOf( num.intValue() + 1 );
} else {
num = Integer.valueOf( 1 );
}
directories.put( path, num );
// What's the locale?
String locale = getLocale( filename );
locales.put( locale, Boolean.TRUE );
if ( locale.charAt( 2 ) != '_' ) {
log.logError( "This i18n locale file is not conform the Kettle standard: " + filename );
}
}
} catch ( Exception e ) {
throw new KettleFileException( "Unable to get all files from directory [" + ROOT + "]", e );
}
}
public void open() {
shell = new Shell( display );
shell.setLayout( new FillLayout() );
shell.setText( APP_NAME );
try {
readFiles( ROOT );
} 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 FillLayout() );
addList();
addGrid();
addListeners();
sashform.setWeights( new int[] { 30, 70 } );
sashform.setVisible( true );
refresh();
BaseStepDialog.setSize( shell );
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();
}
} );
// wOK : nothing yet
// wLocale
wLocale.addSelectionListener( new SelectionAdapter() {
public void widgetSelected( SelectionEvent arg0 ) {
selectLocales();
refreshGrid();
}
} );
wReload.addSelectionListener( new SelectionAdapter() {
public void widgetSelected( SelectionEvent arg0 ) {
int[] idx = wList.getSelectionIndices();
reload();
wList.setSelection( idx );
refreshGrid();
}
} );
wClose.addSelectionListener( new SelectionAdapter() {
public void widgetSelected( SelectionEvent arg0 ) {
quitFile();
}
} );
wUsed.addSelectionListener( new SelectionAdapter() {
public void widgetSelected( SelectionEvent arg0 ) {
refreshGrid();
}
} );
wAvailable.addSelectionListener( new SelectionAdapter() {
public void widgetSelected( SelectionEvent arg0 ) {
refreshGrid();
}
} );
}
public void reload() {
try {
// Clear the hashtables...
clear();
shell.setCursor( display.getSystemCursor( SWT.CURSOR_WAIT ) );
readFiles( ROOT );
shell.setCursor( null );
javaFiles = new Hashtable<String, String>( 500 );
refresh();
} catch ( Exception e ) {
new ErrorDialog( shell, "Error loading data", "There was an unexpected error re-loading the data", e );
}
}
public boolean quitFile() {
WindowProperty winprop = new WindowProperty( shell );
props.setScreen( winprop );
props.saveProps();
shell.dispose();
display.dispose();
return true;
}
private void addList() {
Composite composite = new Composite( sashform, SWT.NONE );
props.setLook( composite );
FillLayout fillLayout = new FillLayout();
fillLayout.marginWidth = Const.FORM_MARGIN;
fillLayout.marginHeight = Const.FORM_MARGIN;
composite.setLayout( fillLayout );
// Make a listbox
wList = new List( composite, SWT.MULTI | SWT.BORDER | SWT.V_SCROLL | SWT.H_SCROLL );
// Add a selection listener.
wList.addSelectionListener( new SelectionAdapter() {
public void widgetSelected( SelectionEvent arg0 ) {
refreshGrid();
}
} );
}
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( " &Reload " );
wLocale = new Button( composite, SWT.NONE );
wLocale.setText( " &Select locale " );
wClose = new Button( composite, SWT.NONE );
wClose.setText( " &Close " );
wVerify = new Button( composite, SWT.CHECK );
wVerify.setText( "&Verify usage" );
wVerify.setSelection( true ); // Check it!
wUsed = new Button( composite, SWT.CHECK );
wUsed.setText( "&Remove used keys" );
wUsed.setSelection( false ); // Check it!
wAvailable = new Button( composite, SWT.CHECK );
wAvailable.setText( "&Check key against other locale" );
wAvailable.setSelection( true ); // Check it!
BaseStepDialog.positionBottomButtons( composite, new Button[] {
wReload, wLocale, wClose, wVerify, wUsed, wAvailable }, Const.MARGIN, null );
ColumnInfo[] colinf =
new ColumnInfo[] {
new ColumnInfo( "Locale", ColumnInfo.COLUMN_TYPE_TEXT, true ),
new ColumnInfo( "Package", ColumnInfo.COLUMN_TYPE_TEXT, true ),
new ColumnInfo( "Class", ColumnInfo.COLUMN_TYPE_TEXT, true ),
new ColumnInfo( "Key", ColumnInfo.COLUMN_TYPE_TEXT, true ),
new ColumnInfo( "Value", ColumnInfo.COLUMN_TYPE_TEXT, true ),
new ColumnInfo( "Used?", ColumnInfo.COLUMN_TYPE_TEXT, true ),
new ColumnInfo( "Not available in", ColumnInfo.COLUMN_TYPE_TEXT, true ), };
wGrid =
new TableView( Variables.getADefaultVariableSpace(), composite, SWT.BORDER
| SWT.FULL_SELECTION | SWT.MULTI, colinf, 0, null, PropsUI.getInstance() );
FormData fdGrid = new FormData();
fdGrid.left = new FormAttachment( 0, 0 );
fdGrid.top = new FormAttachment( 0, 0 );
fdGrid.right = new FormAttachment( 100, 0 );
fdGrid.bottom = new FormAttachment( wReload, -Const.MARGIN * 3 );
wGrid.setLayoutData( fdGrid );
}
public void refresh() {
refreshList();
refreshGrid();
}
public void refreshList() {
// OK, now we have a distinct list of directories or packages to work with...
ArrayList<String> dirList = new ArrayList<String>( directories.keySet() );
Collections.sort( dirList );
// Put it in the listbox:
wList.removeAll();
for ( int i = 0; i < dirList.size(); i++ ) {
wList.add( dirList.get( i ) );
}
}
public void refreshGrid() {
try {
if ( wList.getSelectionCount() > 0 ) {
String[] languages = getSelectedLocale();
System.out.println( "Selected languages: " + languages.length );
shell.setCursor( display.getSystemCursor( SWT.CURSOR_WAIT ) );
// Remove all entries..
wGrid.table.clearAll();
for ( int i = 0; i < wList.getSelectionCount(); i++ ) {
String dir = wList.getSelection()[i];
// Loop over the files and see if it belongs to this directory
for ( String filename : files.keySet() ) {
if ( getPath( filename ).equals( dir ) ) { // yep, add this one
Properties properties = files.get( filename );
ArrayList<Object> entryObjects = new ArrayList<Object>( properties.keySet() );
ArrayList<String> entries = new ArrayList<String>();
for ( Object object : entryObjects ) {
entries.add( (String) object );
}
Collections.sort( entries );
for ( int e = 0; e < entries.size(); e++ ) {
String entry = entries.get( e );
String value = properties.getProperty( entry, "" );
String locale = getLocale( filename );
String classname = getClassname( entry );
String key =
entry.length() > classname.length() ? entry.substring( classname.length() + 1 ) : entry;
boolean systemKey = entry.startsWith( SYSTEM_KEY_PREFIX );
String fileContent = "";
if ( wVerify.getSelection() ) { // check existance of keys in java files...
if ( systemKey ) {
fileContent = "";
} else {
String javaFile = dir + "/" + classname + ".java";
fileContent = javaFiles.get( javaFile );
if ( fileContent == null ) {
fileContent = loadJava( javaFile, filename, entry );
javaFiles.put( javaFile, fileContent );
}
}
}
// Is this a selected locale?
boolean localeSelected = ( locales.get( locale ) ).booleanValue();
if ( localeSelected ) {
String used = "?";
if ( systemKey ) {
used = "System key";
} else {
if ( wVerify.getSelection() ) {
String keyString = "\"" + entry + "\"";
used = fileContent.indexOf( keyString ) >= 0 ? "Y" : "N";
}
}
boolean notUsed = "N".equalsIgnoreCase( used );
if ( key.equalsIgnoreCase( "Log.FinishedProcessing" ) ) {
System.out.println( "Debug!" );
}
String available = checkAvailability( dir, entry, locale, languages );
if ( !wUsed.getSelection() || notUsed ) {
// Add a new line to the grid
//
TableItem item = new TableItem( wGrid.table, SWT.NONE );
int pos = 1;
item.setText( pos++, locale );
item.setText( pos++, dir );
item.setText( pos++, classname );
item.setText( pos++, key );
item.setText( pos++, value );
item.setText( pos++, used );
item.setText( pos++, available );
if ( notUsed ) {
item.setBackground( unusedColor );
}
}
}
}
}
}
}
}
} catch ( Exception e ) {
new ErrorDialog(
shell, "Error loading data", "There was an unexpected error loading data for the translation grid", e );
} finally {
shell.setCursor( null );
}
// See if the grid is not empty: most platforms don't support this...
if ( wGrid.table.getItemCount() == 0 ) {
new TableItem( wGrid.table, SWT.NONE );
}
wGrid.removeEmptyRows();
wGrid.setRowNums();
wGrid.optWidth( true );
// limit width of column 5:
TableColumn col = wGrid.table.getColumn( 5 );
if ( col.getWidth() > 200 ) {
col.setWidth( 200 );
}
}
private String checkAvailability( String dir, String entry, String locale, String[] languages ) {
String available = "Not checked";
// Lookup the key in other languages
boolean first = true;
for ( int x = 0; x < languages.length; x++ ) {
// The properties file:
String propfile =
ROOT + "/" + dir + "/" + MESSAGES_DIR + "/" + MESSAGES_PREFIX + "_" + languages[x] + EXTENSION;
String add = null;
Properties p = files.get( propfile );
if ( p == null ) {
add = languages[x] + " : missing file";
} else {
// OK, is the key present?
String pkey = p.getProperty( entry );
if ( pkey == null ) {
add = languages[x] + " : missing key";
}
}
if ( add != null ) {
if ( first ) {
available = add;
first = false;
} else {
available += ", " + add;
}
}
}
if ( first ) {
available = "All keys are present in the selected " + languages.length + " locale: ";
for ( int a = 0; a < languages.length; a++ ) {
if ( a == 0 ) {
available += languages[a];
} else {
available += ", " + languages[a];
}
}
}
return available;
}
private String loadJava( String javaFile, String propertiesFilename, String entry ) throws KettleFileException {
if ( Utils.isEmpty( entry ) ) {
return "";
}
try {
String filename = ROOT + "/" + javaFile;
StringBuilder content = new StringBuilder( 5000 );
FileInputStream stream = new FileInputStream( filename );
try {
int c = 0;
while ( ( c = stream.read() ) != -1 ) {
content.append( (char) c );
}
} finally {
stream.close();
}
return content.toString();
} catch ( Exception e ) {
throw new KettleFileException( propertiesFilename
+ ": Unable to load file [" + javaFile + "] for key [" + entry + "]", e );
}
}
public String getClassname( String key ) {
int idxDot = key.indexOf( '.' );
if ( idxDot < 0 ) {
return "";
}
return key.substring( 0, idxDot );
}
public String getLocale( String filename ) {
// src/be/ibridge/kettle/i18n/messages/messages_nl_NL.properties
int idx = filename.length() - MESSAGES_DIR.length() - 3 - LOCALE_LENGTH;
return filename.substring( idx, idx + LOCALE_LENGTH );
}
/**
* Get the path until the first occurrence of "/messages/"
*
* @param entry
* @return
*/
private String getPath( String entry ) {
String retval = entry;
int idxRoot = retval.indexOf( ROOT );
if ( idxRoot >= 0 ) {
retval = retval.substring( ROOT.length() + 1 );
}
int idxMess = retval.indexOf( "/" + MESSAGES_DIR + "/" );
if ( idxMess >= 0 ) {
retval = retval.substring( 0, idxMess );
}
return retval;
}
public String[] getAvailableLocale() {
Set<String> set = locales.keySet();
return set.toArray( new String[set.size()] );
}
public String[] getSelectedLocale() {
ArrayList<String> selection = new ArrayList<String>();
String[] locs = getAvailableLocale();
for ( int i = 0; i < locs.length; i++ ) {
if ( ( locales.get( locs[i] ) ).booleanValue() ) {
selection.add( locs[i] );
}
}
return selection.toArray( new String[selection.size()] );
}
public void selectLocales() {
String[] available = getAvailableLocale();
EnterListDialog eld = new EnterListDialog( shell, SWT.NONE, available );
String[] selection = eld.open();
if ( selection != null ) {
for ( int i = 0; i < available.length; i++ ) {
locales.put( available[i], Boolean.FALSE );
}
for ( int i = 0; i < selection.length; i++ ) {
locales.put( selection[i], Boolean.TRUE );
}
}
}
public String toString() {
return APP_NAME;
}
public static void main( String[] args ) {
Display display = new Display();
LogChannelInterface log = new LogChannel( APP_NAME );
PropsUI.init( display, Props.TYPE_PROPERTIES_SPOON );
Translator translator = new Translator( display );
translator.open();
try {
while ( !display.isDisposed() ) {
if ( !display.readAndDispatch() ) {
display.sleep();
}
}
} catch ( Throwable e ) {
log.logError( "An unexpected error occurred : " + e.getMessage() );
log.logError( Const.getStackTracker( e ) );
}
}
}