/*
* Copyright 2012 Vincent Lhote
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package translation.util;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.FilenameFilter;
import java.io.IOException;
import java.text.MessageFormat;
import java.util.Calendar;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import org.apache.commons.lang3.time.DateFormatUtils;
/**
* This class allows the generation of a PO Template file from the tips.txt files,
* and also generate translated tips ({@code tips_XX.txt}) from a PO file (whose name is {@code _XX.po}).
* PO Template and PO files are message catalog used in gettext.
* The duplicates from the tips files should appear only once in the PO Template files.
*
* This class tries to be independent of code, but still needs Apache Commons Lang.
*
* @author Vincent Lhote
* @see <a href="http://www.gnu.org/software/gettext/manual/gettext.html">GNU gettext manual</a>
*/
public class Tips
{
/** Quote char */
private static final char QUOTE = '"';
/**
*
*/
private static final String COMMENT_PREFIX = "#"; //$NON-NLS-1$
private static final String DEFAULT_TIPS_FILENAME = "tips.txt"; //$NON-NLS-1$
/** true to add a message to tips that are not translated, false to copy them as is so they won't appear */
private static final boolean MARK_UNTRANSLATED = true;
public static void generatePOT(File rootDirectory, String potFilename)
{
generatePOT(rootDirectory, potFilename, DEFAULT_TIPS_FILENAME);
}
/**
*
* @param rootDirectory root of the directories to parse
* @param filename the name of the filename to parse
*/
public static void generatePOT(File rootDirectory, String potFilename, String filename)
{
Set<String> tips = new HashSet<>();
// search for each filename in the sub directory of rootDirectory
if (rootDirectory.isDirectory())
{
FilenameFilter filter = new SpecificFilenameFilter(filename);
File[] subfiles = rootDirectory.listFiles();
for (int i = 0; i < subfiles.length; i++)
{
if (subfiles[i].isDirectory())
{
File[] tipsFiles = subfiles[i].listFiles(filter);
for (int j = 0; j < tipsFiles.length; j++)
{
log("Found {0}", tipsFiles[j]);
// for each non comment line of the file, put its content in a Set<String>
try
{
BufferedReader reader = new BufferedReader(new FileReader(tipsFiles[j]));
addTips(tips, reader);
reader.close();
}
catch (FileNotFoundException e)
{
logError("Warning: file found then not found {0}, ignoring this file", tipsFiles[j]);
e.printStackTrace();
}
catch (IOException e)
{
logError("Warning: IO error reading {0}, ignoring this file", tipsFiles[j]);
e.printStackTrace();
}
}
}
}
}
// generate the pot
writePOT(tips, potFilename);
log("Done");
}
/**
* @param tips
* @param potFilename
*/
private static void writePOT(Set<String> tips, String potFilename)
{
File pot = new File(potFilename);
// create parent if necessary
pot.getParentFile().mkdirs();
BufferedWriter bw = null;
try
{
bw = new BufferedWriter(new FileWriter(pot));
writePOT(tips, bw);
log("Wrote {0}", potFilename);
}
catch (IOException e)
{
logError("IO error while writing {0}", pot);
e.printStackTrace();
}
finally
{
try
{
if (bw != null)
bw.close();
}
catch (IOException e)
{
logError("IO error while closing {0}", pot);
e.printStackTrace();
}
}
}
/**
* @param tips
* @param bw
* @throws IOException
*/
private static void writePOT(Set<String> tips, BufferedWriter bw) throws IOException
{
// header stuff
Calendar now = Calendar.getInstance();
bw.write("msgid \"\"\n" + "msgstr \"\"\n" + "\"Project-Id-Version: PCGen-tips 6.x/SVN?\\n\"\n"
+ "\"Report-Msgid-Bugs-To: \\n\"\n" + "\"POT-Creation-Date: "
+ DateFormatUtils.ISO_DATETIME_TIME_ZONE_FORMAT.format(now) + "\\n\"\n"
+ "\"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\\n\"\n"
+ "\"Last-Translator: FULL NAME <EMAIL@ADDRESS>\\n\"\n" + "\"Language-Team: LANGUAGE <LL@li.org>\\n\"\n"
+ "\"MIME-Version: 1.0\\n\"\n" + "\"Content-Type: text/plain; charset=UTF-8\\n\"\n"
+ "\"Content-Transfer-Encoding: 8bit\\n\"\n\n");
// filecontent
MessageFormat msgid = new MessageFormat("msgid \"{0}\""); //$NON-NLS-1$
String msgstr = "msgstr \"\""; //$NON-NLS-1$
for (String tip : tips)
{
bw.write(msgid.format(new Object[]{escape(tip)}));
bw.write("\n");
bw.write(msgstr);
bw.write("\n\n");
}
}
protected static void addTips(Set<String> tips, BufferedReader reader)
{
String line;
try
{
line = reader.readLine();
while (line != null)
{
if (isTip(line))
addTip(tips, line);
line = reader.readLine();
}
}
catch (IOException e)
{
logError("Warning: IO error reading a line, ignoring it");
e.printStackTrace();
}
}
protected static boolean isTip(String line)
{
return line != null && !line.isEmpty() && !line.startsWith(COMMENT_PREFIX);
}
protected static void addTip(Set<String> tips, String tip)
{
tips.add(tip);
}
/**
* Filter on a specific filename.
*/
static class SpecificFilenameFilter implements FilenameFilter
{
private final String filename;
/**
* @param filename
*/
public SpecificFilenameFilter(String filename)
{
this.filename = filename;
}
@Override
public boolean accept(File dir, String name)
{
return filename.equals(name);
}
}
public static void generateTips(File rootDirectory, File translation, String translationName)
{
generateTips(rootDirectory, translation, translationName, DEFAULT_TIPS_FILENAME);
}
/**
*
* @param rootDirectory directory to search in (only done in sub-directories of this)
* @param translation PO file
* @param translationName name for new translation filename (like tips_fr.txt)
* @param originalName original filename (like tips.txt)
*/
public static void generateTips(File rootDirectory, File translation, String translationName, String originalName)
{
int statUntranslated = 0, statTranslated = 0;
// load stuff from the PO catalog file
Map<String, String> tipsTranslated = new HashMap<>();
BufferedReader translationReader = null;
try
{
translationReader = new BufferedReader(new FileReader(translation));
String line = translationReader.readLine();
String key = null;
StringBuilder str = new StringBuilder();
while (line != null)
{
if (line.startsWith("msgid"))
{
if (key != null)
{
// add previous appended translation with the id in the map
tipsTranslated.put(key, str.toString());
if (str.toString().isEmpty())
statUntranslated++;
else statTranslated++;
str = new StringBuilder();
}
key = line.substring(line.indexOf(QUOTE) + 1, line.lastIndexOf(QUOTE));
}
// TODO remove escape quote?
if (line.startsWith("msgstr") || line.startsWith("\""))
{
String substring = line.substring(line.indexOf(QUOTE) + 1, line.lastIndexOf(QUOTE));
substring = removeEscaped(substring);
str.append(substring);
}
line = translationReader.readLine();
}
// adds last key/translation
if (key != null)
{
tipsTranslated.put(key, str.toString());
if (str.toString().isEmpty())
statUntranslated++;
else statTranslated++;
}
}
catch (IOException e)
{
e.printStackTrace();
}
finally
{
if (translationReader != null)
try
{
translationReader.close();
}
catch (IOException e)
{
// TODO Auto-generated catch block
e.printStackTrace();
}
}
log("Translated tips: {0}", statTranslated);
log("Untranslated tips: {0}", statUntranslated);
// find the originalName file in each subdir of root
if (rootDirectory.isDirectory())
{
FilenameFilter filter = new SpecificFilenameFilter(originalName);
File[] subfiles = rootDirectory.listFiles();
for (int i = 0; i < subfiles.length; i++)
{
if (subfiles[i].isDirectory())
{
File[] tipsFiles = subfiles[i].listFiles(filter);
for (int j = 0; j < tipsFiles.length; j++)
{
File newFile = new File(subfiles[i], translationName);
log("Found {0}, creating {1}", tipsFiles[j], newFile);
BufferedWriter bw = null;
BufferedReader reader = null;
try
{
reader = new BufferedReader(new FileReader(tipsFiles[j]));
bw = new BufferedWriter(new FileWriter(newFile));
String readLine = reader.readLine();
while (readLine != null)
{
if (isTip(readLine))
{
String translatedLine = tipsTranslated.get(readLine);
if (translatedLine == null)
{
log("null translated line in {1}, original {0}", readLine, translation);
translatedLine = readLine;
}
else if (translatedLine.isEmpty() && MARK_UNTRANSLATED)
{
translatedLine = "<em>Not yet translated</em><br>" + readLine;
}
bw.write(translatedLine);
}
else bw.write(readLine);
bw.write("\n");
readLine = reader.readLine();
}
}
catch (FileNotFoundException e)
{
// TODO Auto-generated catch block
e.printStackTrace();
}
catch (IOException e)
{
// TODO Auto-generated catch block
e.printStackTrace();
}
finally
{
try
{
if (reader != null)
reader.close();
}
catch (IOException e)
{
// TODO Auto-generated catch block
e.printStackTrace();
}
try
{
if (bw != null)
bw.close();
}
catch (IOException e)
{
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
}
}
log("Done");
}
/* Escape related methods */
/**
* Handle string escapes in the PO files.
* @param string
* @return non escaped string
*/
@SuppressWarnings("nls")
protected static String removeEscaped(String string)
{
return string.replaceAll("\\\\\'", "'").replaceAll("\\\\\"", "\"").replaceAll("\\\\\\\\", "\\\\");
}
/**
* @param string
* @return
*/
@SuppressWarnings("nls")
protected static String escape(String string)
{
return string.replaceAll("\\\\", "\\\\\\\\").replaceAll("\'", "\\\\\'").replaceAll("\"", "\\\\\"");
}
/* main and related methods */
public static void main(String[] args)
{
if (args.length == 0)
{
logError("Missing argument");
usage();
return;
}
// TODO detect what to do based on arguments
if ("POT".equals(args[0]))
{
// TODO handle missing argument case
if (args.length == 4)
generatePOT(new File(args[1]), args[2], args[3]);
else generatePOT(new File(args[1]), args[2]);
}
else if ("tips".equals(args[0]))
{
// TODO handle missing argument case
if (args.length == 5)
generateTips(new File(args[1]), new File(args[2]), args[3], args[4]);
else generateTips(new File(args[1]), new File(args[2]), args[3]);
}
else
{
logError("Unknown command");
usage();
}
}
/**
*
*/
private static void usage()
{
log("Usage:");
log("\t<command> POT rootDirectory potFilename [tipsFilename]");
log("\t<command> tips rootDirectory translationFilename TranslationFilename(ie. tips_xx.txt) [tipsFilename]");
log("\tIf tipsFilename is missing, the default ({0}) is used.", DEFAULT_TIPS_FILENAME);
}
/* Logging methods. */
/**
* Log a message to the standard output
* @param string message pattern
* @param o arguments
* @see MessageFormat#format(String, Object...)
*/
private static void log(String string, Object... o)
{
System.out.println(MessageFormat.format(string, o));
}
/**
* Log a message to the error output
* @param string message pattern
* @param o arguments
* @see MessageFormat#format(String, Object...)
*/
private static void logError(String string, Object... o)
{
System.err.println(MessageFormat.format(string, o));
}
}