001/* 002 * Licensed under the Apache License, Version 2.0 (the "License"); 003 * you may not use this file except in compliance with the License. 004 * You may obtain a copy of the License at 005 * 006 * http://www.apache.org/licenses/LICENSE-2.0 007 * 008 * Unless required by applicable law or agreed to in writing, software 009 * distributed under the License is distributed on an "AS IS" BASIS, 010 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 011 * See the License for the specific language governing permissions and 012 * limitations under the License. 013 */ 014package org.apache.wiki; 015 016import java.io.FileNotFoundException; 017import java.io.IOException; 018import java.io.InputStream; 019import java.nio.charset.StandardCharsets; 020import java.nio.file.Files; 021import java.nio.file.Paths; 022import java.util.ArrayList; 023import java.util.Collections; 024import java.util.Enumeration; 025import java.util.HashMap; 026import java.util.Iterator; 027import java.util.List; 028import java.util.Map; 029import java.util.Properties; 030import java.util.TreeSet; 031 032 033/** 034 * Simple utility that shows you a sorted list of property differences between 035 * the 'default en' and a given i18n file. It also warns if there are any duplicates. 036 * <p> 037 * The first argument is the language, and it is mandatory. 038 * The second argument is the path. If the path is not defined, uses current path (.) 039 * <p> 040 * For example (if you're compiling your classes to "classes"): 041 * <code> 042 * java -cp classes TranslationsCheck fi 043 * </code> 044 */ 045public class TranslationsCheck { 046 047 private final static String[] LANGS = { "de", "en", "es", "fi", "fr", "it", "nl", "pt_BR", "ru", "zh_CN" }; 048 private final static String SITE_I18N_ROW = 049 "<tr%s>\n" + 050 " <td title=\"Available sets of core WikiPages for %s\"><a class=\"external\" href=\"https://search.maven.org/artifact/org.apache.jspwiki.wikipages/jspwiki-wikipages-%s\">%s</a></td>\n" + 051 " <td>%d%%</td>\n" + 052 " <td>%d</td>\n" + 053 " <td>%d</td>\n" + 054 "</tr>\n"; 055 056 private final TreeSet< String > allProps = new TreeSet<>(); // sorted, no duplicates 057 private final TreeSet< String > duplProps = new TreeSet<>(); 058 059 // Change these to your settings... 060 String base = "."; 061 String suffix = null; 062 063 public static void main( final String[] args ) throws IOException { 064 final TranslationsCheck translations = new TranslationsCheck(); 065 if( args.length == 0 ) { 066 System.out.println( "Usage: java TranslationsCheck <language> [<path>]" ); 067 System.out.println( "Example: java TranslationsCheck nl [jspwiki-main/src/main/resources]" ); 068 System.out.println( "To output site i18n info use java TranslationsCheck site [<path>]" ); 069 return; 070 } 071 translations.suffix = args[ 0 ]; 072 if( args.length >= 2 ) { 073 translations.base = args[ 1 ]; 074 } 075 076 if( "site".equals( translations.suffix ) ) { 077 String site = ""; 078 for( int i = 0; i < LANGS.length; i++ ) { 079 translations.suffix = LANGS[ i ]; 080 site += translations.check( i ); 081 } 082 site += "</table>\n" + // close table and formatting divs 083 "</div>\n" + 084 "</div>\n" + 085 "</div>"; 086 Files.write( Paths.get( "./i18n-table.txt" ), site.getBytes( StandardCharsets.UTF_8 ) ); 087 } else { 088 translations.check( -1 ); 089 } 090 } 091 092 String check( final int lang ) throws IOException { 093 // System.out.println( "Using code base " + Release.VERSTR ); 094 System.out.println( "Internationalization property file differences between 'default en' and '" + suffix + "' following:\n" ); 095 096 final String fileSuffix = ( "en".equals( suffix ) ) ? "" : "_" + suffix; 097 final Map< String, Integer > coreMetrics = checkFile( "/CoreResources.properties", "/CoreResources" + fileSuffix + ".properties" ); 098 final Map< String, Integer > templateMetrics = checkFile( "/templates/default.properties", "/templates/default" + fileSuffix + ".properties" ); 099 final Map< String, Integer > pluginMetrics = checkFile( "/plugin/PluginResources.properties", "/plugin/PluginResources" + fileSuffix + ".properties" ); 100 101 if( lang >= 0 ) { 102 final int expected = coreMetrics.get( "expected" ) + templateMetrics.get( "expected" ) + pluginMetrics.get( "expected" ); 103 final int missing = coreMetrics.get( "missing" ) + templateMetrics.get( "missing" ) + pluginMetrics.get( "missing" ); 104 final int completed = 100 * ( expected - missing ) / expected; 105 final int outdated = coreMetrics.get( "outdated" ) + templateMetrics.get( "outdated" ) + pluginMetrics.get( "outdated" ); 106 final String odd = lang %2 == 0 ? " class=\"odd\"" : ""; // 0 first row 107 108 return String.format( SITE_I18N_ROW, odd, suffix, suffix, suffix, completed, missing, outdated ); 109 } 110 return ""; 111 } 112 113 Map< String, Integer > checkFile( final String en, final String lang ) throws IOException { 114 final Map< String, Integer > metrics = new HashMap<>(); 115 try { 116 metrics.putAll( diff( en, lang ) ); 117 metrics.put( "duplicates", detectDuplicates( lang ) ); 118 } catch( final FileNotFoundException e ) { 119 System.err.println( "Unable to locate " + lang ); 120 } 121 System.out.println( "Duplicates overall (two or more occurences):" ); 122 System.out.println( "--------------------------------------------" ); 123 final Iterator< String > iter = duplProps.iterator(); 124 if( duplProps.size() == 0 ) { 125 System.out.println( "(none)" ); 126 } else { 127 while( iter.hasNext() ) { 128 System.out.println( iter.next() ); 129 } 130 } 131 System.out.println( "" ); 132 return metrics; 133 } 134 135 public Map< String, Integer > diff( final String source1, final String source2 ) throws IOException { 136 int missing = 0, outdated = 0; 137 // Standard Properties 138 final Properties p1 = new Properties(); 139 p1.load( getResourceAsStream( source1 ) ); 140 141 final Properties p2 = new Properties(); 142 p2.load( getResourceAsStream( source2 ) ); 143 144 final String msg = "Checking " + source2 + "..."; 145 System.out.println( msg ); 146 147 Iterator< String > iter = sortedNames( p1 ).iterator(); 148 while( iter.hasNext() ) { 149 final String name = iter.next(); 150 final String value = p1.getProperty( name ); 151 152 if( p2.get( name ) == null ) { 153 missing++; 154 if( missing == 1 ) { 155 System.out.println( "\nMissing:" ); 156 System.out.println( "--------" ); 157 } 158 System.out.println( name + " = " + value ); 159 } 160 } 161 if( missing > 0 ) { 162 System.out.println( "" ); 163 } 164 165 iter = sortedNames( p2 ).iterator(); 166 while( iter.hasNext() ) { 167 final String name = iter.next(); 168 final String value = p2.getProperty( name ); 169 170 if( p1.get( name ) == null ) { 171 outdated++; 172 if( outdated == 1 ) { 173 System.out.println( "\nOutdated or superfluous:" ); 174 System.out.println( "------------------------" ); 175 } 176 System.out.println( name + " = " + value ); 177 } 178 } 179 if( outdated > 0 ) { 180 System.out.println( "" ); 181 } 182 183 final Map< String, Integer > diff = new HashMap<>( 2 ); 184 diff.put( "expected", p1.size() ); 185 diff.put( "missing", missing ); 186 diff.put( "outdated", outdated ); 187 return diff; 188 } 189 190 private List< String > sortedNames( final Properties p ) { 191 final List< String > list = new ArrayList<>(); 192 final Enumeration< ? > iter = p.propertyNames(); 193 while( iter.hasMoreElements() ) { 194 list.add( ( String )iter.nextElement() ); 195 } 196 197 Collections.sort( list ); 198 return list; 199 } 200 201 public int detectDuplicates( final String source ) throws IOException { 202 final Properties p = new Properties(); 203 p.load( getResourceAsStream( source ) ); 204 final Enumeration< ? > iter = p.propertyNames(); 205 String currentStr; 206 while( iter.hasMoreElements() ) { 207 currentStr = ( String )iter.nextElement(); 208 if( !allProps.add( currentStr ) ) { 209 duplProps.add( currentStr ); 210 } 211 } 212 return duplProps.size(); 213 } 214 215 InputStream getResourceAsStream( final String source ) { 216 return TranslationsCheck.class.getClassLoader().getResourceAsStream( base + source ); 217 } 218 219}