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; 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 final StringBuilder site = new StringBuilder(); 078 for( int i = 0; i < LANGS.length; i++ ) { 079 translations.suffix = LANGS[ i ]; 080 site.append(translations.check(i)); 081 } 082 site.append("</table>\n" + // close table and formatting divs 083 "</div>\n" + "</div>\n" + "</div>"); 084 Files.write( Paths.get( "./i18n-table.txt" ), site.toString().getBytes( StandardCharsets.UTF_8 ) ); 085 } else { 086 translations.check( -1 ); 087 } 088 } 089 090 String check( final int lang ) throws IOException { 091 // System.out.println( "Using code base " + Release.VERSTR ); 092 System.out.println( "Internationalization property file differences between 'default en' and '" + suffix + "' following:\n" ); 093 094 final String fileSuffix = ( "en".equals( suffix ) ) ? "" : "_" + suffix; 095 final Map< String, Integer > coreMetrics = checkFile( "/CoreResources.properties", "/CoreResources" + fileSuffix + ".properties" ); 096 final Map< String, Integer > templateMetrics = checkFile( "/templates/default.properties", "/templates/default" + fileSuffix + ".properties" ); 097 final Map< String, Integer > pluginMetrics = checkFile( "/plugin/PluginResources.properties", "/plugin/PluginResources" + fileSuffix + ".properties" ); 098 099 if( lang >= 0 ) { 100 final int expected = coreMetrics.get( "expected" ) + templateMetrics.get( "expected" ) + pluginMetrics.get( "expected" ); 101 final int missing = coreMetrics.get( "missing" ) + templateMetrics.get( "missing" ) + pluginMetrics.get( "missing" ); 102 final int completed = 100 * ( expected - missing ) / expected; 103 final int outdated = coreMetrics.get( "outdated" ) + templateMetrics.get( "outdated" ) + pluginMetrics.get( "outdated" ); 104 final String odd = lang %2 == 0 ? " class=\"odd\"" : ""; // 0 first row 105 106 return String.format( SITE_I18N_ROW, odd, suffix, suffix, suffix, completed, missing, outdated ); 107 } 108 return ""; 109 } 110 111 Map< String, Integer > checkFile( final String en, final String lang ) throws IOException { 112 final Map< String, Integer > metrics = new HashMap<>(); 113 try { 114 metrics.putAll( diff( en, lang ) ); 115 metrics.put( "duplicates", detectDuplicates( lang ) ); 116 } catch( final FileNotFoundException e ) { 117 System.err.println( "Unable to locate " + lang ); 118 } 119 System.out.println( "Duplicates overall (two or more occurences):" ); 120 System.out.println( "--------------------------------------------" ); 121 final Iterator< String > iter = duplProps.iterator(); 122 if( duplProps.size() == 0 ) { 123 System.out.println( "(none)" ); 124 } else { 125 while( iter.hasNext() ) { 126 System.out.println( iter.next() ); 127 } 128 } 129 System.out.println( ); 130 return metrics; 131 } 132 133 public Map< String, Integer > diff( final String source1, final String source2 ) throws IOException { 134 int missing = 0, outdated = 0; 135 // Standard Properties 136 final Properties p1 = new Properties(); 137 p1.load( getResourceAsStream( source1 ) ); 138 139 final Properties p2 = new Properties(); 140 p2.load( getResourceAsStream( source2 ) ); 141 142 final String msg = "Checking " + source2 + "..."; 143 System.out.println( msg ); 144 145 Iterator< String > iter = sortedNames( p1 ).iterator(); 146 while( iter.hasNext() ) { 147 final String name = iter.next(); 148 final String value = p1.getProperty( name ); 149 150 if( p2.get( name ) == null ) { 151 missing++; 152 if( missing == 1 ) { 153 System.out.println( "\nMissing:" ); 154 System.out.println( "--------" ); 155 } 156 System.out.println( name + " = " + value ); 157 } 158 } 159 if( missing > 0 ) { 160 System.out.println(); 161 } 162 163 iter = sortedNames( p2 ).iterator(); 164 while( iter.hasNext() ) { 165 final String name = iter.next(); 166 final String value = p2.getProperty( name ); 167 168 if( p1.get( name ) == null ) { 169 outdated++; 170 if( outdated == 1 ) { 171 System.out.println( "\nOutdated or superfluous:" ); 172 System.out.println( "------------------------" ); 173 } 174 System.out.println( name + " = " + value ); 175 } 176 } 177 if( outdated > 0 ) { 178 System.out.println(); 179 } 180 181 final Map< String, Integer > diff = new HashMap<>( 2 ); 182 diff.put( "expected", p1.size() ); 183 diff.put( "missing", missing ); 184 diff.put( "outdated", outdated ); 185 return diff; 186 } 187 188 private List< String > sortedNames( final Properties p ) { 189 final List< String > list = new ArrayList<>(); 190 final Enumeration< ? > iter = p.propertyNames(); 191 while( iter.hasMoreElements() ) { 192 list.add( ( String )iter.nextElement() ); 193 } 194 195 Collections.sort( list ); 196 return list; 197 } 198 199 public int detectDuplicates( final String source ) throws IOException { 200 final Properties p = new Properties(); 201 p.load( getResourceAsStream( source ) ); 202 final Enumeration< ? > iter = p.propertyNames(); 203 String currentStr; 204 while( iter.hasMoreElements() ) { 205 currentStr = ( String )iter.nextElement(); 206 if( !allProps.add( currentStr ) ) { 207 duplProps.add( currentStr ); 208 } 209 } 210 return duplProps.size(); 211 } 212 213 InputStream getResourceAsStream( final String source ) { 214 return TranslationsCheck.class.getClassLoader().getResourceAsStream( base + source ); 215 } 216 217}