diff --git a/docs/manual/OptionalTasks/translate.html b/docs/manual/OptionalTasks/translate.html new file mode 100644 index 000000000..7df9d14d5 --- /dev/null +++ b/docs/manual/OptionalTasks/translate.html @@ -0,0 +1,157 @@ + + +
+ +Identifies keys in files delimited by special tokens +and translates them with values read from resource bundles. +
++A resource bundle contains locale-specific key-value pairs. +A resource bundle is a hierarchical set of property files. +A bundle name makes up its base family name. Each file that +makes up this bundle has this name plus its locale. For example, +if the resource bundle name is MyResources, the file that contains +German text will take the name MyResources_de. In addition to +language, country and variant are also used to form the files in +the bundle. +
++The resource bundle lookup searches for resource files with various +suffixes on the basis of (1) the desired locale and (2) the default +locale (basebundlename), in the following order from lower-level +(more specific) to parent-level (less specific): +
++basebundlename + "_" + language1 + "_" + country1 + "_" + variant1 +basebundlename + "_" + language1 + "_" + country1 +basebundlename + "_" + language1 +basebundlename +basebundlename + "_" + language2 + "_" + country2 + "_" + variant2 +basebundlename + "_" + language2 + "_" + country2 +basebundlename + "_" + language2 ++
+The file names generated thus are appended with the string ".properties" +to make up the file names that are to be used. +
++File encoding is supported. The encoding scheme of the source files, +destination files and the bundle files can be specified. + +Destination files can be exlicitly overwritten using the +forceoverwrite attribute. If forceoverwrite +is false, the destination file is overwritten only if either the +source file or any of the files that make up the bundle have been +modified after the destination file was last modified. +
+FileSets are used to select files to +translate. +
+| Attribute | +Description | +Required | +
| todir | +Destination directory where destination files are + to be created. | +Yes | +
| starttoken | +The starting token to identify keys. | +Yes | +
| endtoken | +The ending token to identify keys. | +Yes | +
| bundle | +Family name of resource bundle. | +Yes | +
| bundlelanguage | ++ Locale specific language of resource bundle. Defaults to + default locale's language. + | +No | +
| bundlecountry | ++ Locale specific country of resource bundle. Defaults to + default locale's country. + | +No | +
| bundlevariant | ++ Locale specific variant of resource bundle. Defaults to + the default variant of the country and language being used. + | +No | +
| srcencoding | +Source file encoding scheme. Defaults to + system default file encoding. | +No | +
| destencoding | +Destination file encoding scheme. Defaults to + source file encoding. | +No | +
| bundleencoding | +Resource Bundle file encoding scheme. Defaults to + source file encoding. | +No | +
| forceoverwrite | +Overwrite existing files even if the destination + files are newer. Defaults to "no". | +No | +
FileSets are used to select files that + contain keys for which value translated files are to be generated. +
+Translate source file encoded in english into its japanese +equivalent using a resource bundle encoded in japanese. +
+
+ <translate toDir="$(dest.dir}/ja"
+ starttoken="#"
+ endtoken="#"
+ bundle="resource/BaseResource"
+ bundlelanguage="ja"
+ forceoverwrite="yes"
+ srcencoding="ISO8859_1"
+ destencoding="SJIS"
+ bundleencoding="SJIS">
+ <fileset dir="${src.dir}">
+ <include name="**/*.jsp"/>
+ </fileset>
+ </translate>
diff --git a/docs/manual/optionaltasklist.html b/docs/manual/optionaltasklist.html
index 8dbe2e71d..f3e50db99 100644
--- a/docs/manual/optionaltasklist.html
+++ b/docs/manual/optionaltasklist.html
@@ -46,6 +46,7 @@
Stylebook
Telnet
Test
+Translate
Visual Age for Java Tasks
Microsoft Visual SourceSafe Tasks
Weblogic JSP Compiler
diff --git a/src/main/org/apache/tools/ant/taskdefs/optional/i18n/Translate.java b/src/main/org/apache/tools/ant/taskdefs/optional/i18n/Translate.java
new file mode 100644
index 000000000..12e5ba40d
--- /dev/null
+++ b/src/main/org/apache/tools/ant/taskdefs/optional/i18n/Translate.java
@@ -0,0 +1,565 @@
+/*
+ * The Apache Software License, Version 1.1
+ *
+ * Copyright (c) 1999 The Apache Software Foundation. All rights
+ * reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in
+ * the documentation and/or other materials provided with the
+ * distribution.
+ *
+ * 3. The end-user documentation included with the redistribution, if
+ * any, must include the following acknowlegement:
+ * "This product includes software developed by the
+ * Apache Software Foundation (http://www.apache.org/)."
+ * Alternately, this acknowlegement may appear in the software itself,
+ * if and wherever such third-party acknowlegements normally appear.
+ *
+ * 4. The names "The Jakarta Project", "Ant", and "Apache Software
+ * Foundation" must not be used to endorse or promote products derived
+ * from this software without prior written permission. For written
+ * permission, please contact apache@apache.org.
+ *
+ * 5. Products derived from this software may not be called "Apache"
+ * nor may "Apache" appear in their names without prior written
+ * permission of the Apache Group.
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
+ * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+ * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ * ====================================================================
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals on behalf of the Apache Software Foundation. For more
+ * information on the Apache Software Foundation, please see
+ * .
+ */
+package org.apache.tools.ant.taskdefs.optional.i18n;
+
+import java.io.*;
+import java.util.*;
+import org.apache.tools.ant.*;
+import org.apache.tools.ant.types.*;
+import org.apache.tools.ant.util.*;
+import org.apache.tools.ant.taskdefs.MatchingTask;
+
+/**
+ * Translates text embedded in files using Resource Bundle files.
+ *
+ * @author Magesh Umasankar
+ */
+public class Translate extends MatchingTask {
+
+ /**
+ * Family name of resource bundle
+ */
+ private String bundle;
+ /**
+ * Locale specific language of the resource bundle
+ */
+ private String bundleLanguage;
+ /**
+ * Locale specific country of the resource bundle
+ */
+ private String bundleCountry;
+ /**
+ * Locale specific variant of the resource bundle
+ */
+ private String bundleVariant;
+ /**
+ * Destination directory
+ */
+ private File toDir;
+ /**
+ * Source file encoding scheme
+ */
+ private String srcEncoding;
+ /**
+ * Destination file encoding scheme
+ */
+ private String destEncoding;
+ /**
+ * Resource Bundle file encoding scheme, defaults to srcEncoding
+ */
+ private String bundleEncoding;
+ /**
+ * Starting token to identify keys
+ */
+ private String startToken;
+ /**
+ * Ending token to identify keys
+ */
+ private String endToken;
+ /**
+ * Create new destination file? Defaults to false.
+ */
+ private boolean forceOverwrite;
+ /**
+ * Vector to hold source file sets.
+ */
+ private Vector filesets = new Vector();
+ /**
+ * Holds key value pairs loaded from resource bundle file
+ */
+ private Hashtable resourceMap = new Hashtable();
+ /**
+ * Generated locale based on user attributes
+ */
+ private Locale locale;
+ /**
+ * Used to resolve file names.
+ */
+ private FileUtils fileUtils = FileUtils.newFileUtils();
+ /**
+ * Last Modified Timestamp of resource bundle file being used.
+ */
+ private long[] bundleLastModified = new long[7];
+ /**
+ * Last Modified Timestamp of source file being used.
+ */
+ private long srcLastModified;
+ /**
+ * Last Modified Timestamp of destination file being used.
+ */
+ private long destLastModified;
+
+ /**
+ * Sets Family name of resource bundle
+ */
+ public void setBundle(String bundle) {
+ this.bundle = bundle;
+ }
+
+ /**
+ * Sets locale specific language of resource bundle
+ */
+ public void setBundleLanguage(String bundleLanguage ) {
+ this.bundleLanguage = bundleLanguage;
+ }
+
+ /**
+ * Sets locale specific country of resource bundle
+ */
+ public void setBundleCountry(String bundleCountry) {
+ this.bundleCountry = bundleCountry;
+ }
+
+ /**
+ * Sets locale specific variant of resource bundle
+ */
+ public void setBundleVariant(String bundleVariant) {
+ this.bundleVariant = bundleVariant;
+ }
+
+ /**
+ * Sets Destination directory
+ */
+ public void setToDir(File toDir) {
+ this.toDir = toDir;
+ }
+
+ /**
+ * Sets starting token to identify keys
+ */
+ public void setStartToken(String startToken) {
+ this.startToken = startToken;
+ }
+
+ /**
+ * Sets ending token to identify keys
+ */
+ public void setEndToken(String endToken) {
+ this.endToken = endToken;
+ }
+
+ /**
+ * Sets source file encoding scheme
+ */
+ public void setSrcEncoding(String srcEncoding) {
+ this.srcEncoding = srcEncoding;
+ }
+
+ /**
+ * Sets destination file encoding scheme. Defaults to source file
+ * encoding
+ */
+ public void setDestEncoding(String destEncoding) {
+ this.destEncoding = destEncoding;
+ }
+
+ /**
+ * Sets Resource Bundle file encoding scheme
+ */
+ public void setBundleEncoding(String bundleEncoding) {
+ this.bundleEncoding = bundleEncoding;
+ }
+
+ /**
+ * Overwrite existing file irrespective of whether it is newer than
+ * the source file as well as the resource bundle file? Defaults to
+ * false.
+ */
+ public void setForceOverwrite(boolean forceOverwrite) {
+ this.forceOverwrite = forceOverwrite;
+ }
+
+ /**
+ * Adds a set of files (nested fileset attribute).
+ */
+ public void addFileset(FileSet set) {
+ filesets.addElement(set);
+ }
+
+ /**
+ * Check attributes values, load resource map and translate
+ */
+ public void execute() throws BuildException {
+ if (bundle == null) {
+ throw new BuildException("The bundle attribute must be set.",
+ location);
+ }
+
+ if (startToken == null) {
+ throw new BuildException("The starttoken attribute must be set.",
+ location);
+ }
+
+ if (endToken == null) {
+ throw new BuildException("The endtoken attribute must be set.",
+ location);
+ }
+
+ if (bundleLanguage == null) {
+ Locale l = Locale.getDefault();
+ bundleLanguage = l.getLanguage();
+ }
+
+ if (bundleCountry == null) {
+ bundleCountry = Locale.getDefault().getCountry();
+ }
+
+ locale = new Locale(bundleLanguage, bundleCountry);
+
+ if (bundleVariant == null) {
+ Locale l = new Locale(bundleLanguage, bundleCountry);
+ bundleVariant = l.getVariant();
+ }
+
+ if (toDir == null) {
+ throw new BuildException("The todir attribute must be set.",
+ location);
+ }
+
+ if (!toDir.exists()) {
+ toDir.mkdirs();
+ } else {
+ if (toDir.isFile()) {
+ throw new BuildException(toDir + " is not a directory");
+ }
+ }
+
+ if (srcEncoding == null) {
+ srcEncoding = System.getProperty("file.encoding");
+ }
+
+ if (destEncoding == null) {
+ destEncoding = srcEncoding;
+ }
+
+ if (bundleEncoding == null) {
+ bundleEncoding = srcEncoding;
+ }
+
+ loadResourceMaps();
+
+ translate();
+ }
+
+ /**
+ * Load resource maps based on resource bundle encoding scheme.
+ * The resource bundle lookup searches for resource files with various
+ * suffixes on the basis of (1) the desired locale and (2) the default
+ * locale (basebundlename), in the following order from lower-level
+ * (more specific) to parent-level (less specific):
+ *
+ * basebundlename + "_" + language1 + "_" + country1 + "_" + variant1
+ * basebundlename + "_" + language1 + "_" + country1
+ * basebundlename + "_" + language1
+ * basebundlename
+ * basebundlename + "_" + language2 + "_" + country2 + "_" + variant2
+ * basebundlename + "_" + language2 + "_" + country2
+ * basebundlename + "_" + language2
+ *
+ * To the generated name, a ".properties" string is appeneded and
+ * once this file is located, it is treated just like a properties file
+ * but with bundle encoding also considered while loading.
+ */
+ public void loadResourceMaps() throws BuildException {
+ Locale locale = new Locale(bundleLanguage,
+ bundleCountry,
+ bundleVariant);
+ String language = locale.getLanguage().length() > 0 ?
+ "_" + locale.getLanguage() :
+ "";
+ String country = locale.getCountry().length() > 0 ?
+ "_" + locale.getCountry() :
+ "";
+ String variant = locale.getVariant().length() > 0 ?
+ "_" + locale.getVariant() :
+ "";
+ String bundleFile = bundle + language + country + variant;
+ processBundle(bundleFile, 0, false);
+
+ bundleFile = bundle + language + country;
+ processBundle(bundleFile, 1, false);
+
+ bundleFile = bundle + language;
+ processBundle(bundleFile, 2, false);
+
+ bundleFile = bundle;
+ processBundle(bundleFile, 3, false);
+
+ //Load default locale bundle files
+ //using default file encoding scheme.
+ locale = Locale.getDefault();
+
+ language = locale.getLanguage().length() > 0 ?
+ "_" + locale.getLanguage() :
+ "";
+ country = locale.getCountry().length() > 0 ?
+ "_" + locale.getCountry() :
+ "";
+ variant = locale.getVariant().length() > 0 ?
+ "_" + locale.getVariant() :
+ "";
+ bundleEncoding = System.getProperty("file.encoding");
+
+ bundleFile = bundle + language + country + variant;
+ processBundle(bundleFile, 4, false);
+
+ bundleFile = bundle + language + country;
+ processBundle(bundleFile, 5, false);
+
+ bundleFile = bundle + language;
+ processBundle(bundleFile, 6, true);
+ }
+
+ /**
+ * Process each file that makes up this bundle.
+ */
+ private void processBundle(String bundleFile, int i,
+ boolean checkLoaded) throws BuildException {
+ boolean loaded = false;
+ bundleFile += ".properties";
+ FileInputStream ins = null;
+ try {
+ ins = new FileInputStream(bundleFile);
+ loaded = true;
+ bundleLastModified[i] = new File(bundleFile).lastModified();
+ log("Using " + bundleFile, Project.MSG_DEBUG);
+ loadResourceMap(ins);
+ } catch (IOException ioe) {
+ log(bundleFile + " not found.", Project.MSG_DEBUG);
+ //if all resource files associated with this bundle
+ //have been scanned for and still not able to
+ //find a single resrouce file, throw exception
+ if (!loaded && checkLoaded) {
+ throw new BuildException(ioe.getMessage(), location);
+ }
+ }
+ }
+
+ /**
+ * Load resourceMap with key value pairs. Values of existing keys
+ * are not overwritten. Bundle's encoding scheme is used.
+ */
+ private void loadResourceMap(FileInputStream ins) throws BuildException {
+ try {
+ BufferedReader in = null;
+ InputStreamReader isr = new InputStreamReader(ins, bundleEncoding);
+ in = new BufferedReader(isr);
+ String line = null;
+ while((line = in.readLine()) != null) {
+ //So long as the line isn't empty and isn't a comment...
+ if(line.trim().length() > 1 &&
+ ('#' != line.charAt(0) || '!' != line.charAt(0))) {
+ //Legal Key-Value separators are :, = and white space.
+ int sepIndex = line.indexOf('=');
+ if (-1 == sepIndex) {
+ sepIndex = line.indexOf(':');
+ }
+ if (-1 == sepIndex) {
+ for (int k = 0; k < line.length(); k++) {
+ if (Character.isSpaceChar(line.charAt(k))) {
+ sepIndex = k;
+ break;
+ }
+ }
+ }
+ //Only if we do have a key is there going to be a value
+ if (-1 != sepIndex) {
+ String key = line.substring(0, sepIndex).trim();
+ String value = line.substring(sepIndex + 1).trim();
+ //Handle line continuations, if any
+ while (value.endsWith("\\")) {
+ value = value.substring(0, value.length() - 1);
+ if ((line = in.readLine()) != null) {
+ value = value + line.trim();
+ } else {
+ break;
+ }
+ }
+ if (key.length() > 0) {
+ //Has key already been loaded into resourceMap?
+ if (resourceMap.get(key) == null) {
+ resourceMap.put(key, value);
+ }
+ }
+ }
+ }
+ }
+ if(in != null) {
+ in.close();
+ }
+ } catch (IOException ioe) {
+ throw new BuildException(ioe.getMessage(), location);
+ }
+ }
+
+ /**
+ * Reads source file line by line using the source encoding and
+ * searches for keys that are sandwiched between the startToken
+ * and endToken. The values for these keys are looked up from
+ * the hashtable and substituted. If the hashtable doesn't
+ * contain the key, they key itself is used as the value.
+ * Detination files and directories are created as needed.
+ * The destination file is overwritten only if
+ * the forceoverwritten attribute is set to true if
+ * the source file or any associated bundle resource file is
+ * newer than the destination file.
+ */
+ private void translate() throws BuildException {
+ for (int i = 0; i < filesets.size(); i++) {
+ FileSet fs = (FileSet) filesets.elementAt(i);
+ DirectoryScanner ds = fs.getDirectoryScanner(project);
+ String[] srcFiles = ds.getIncludedFiles();
+ for (int j = 0; j < srcFiles.length; j++) {
+ try {
+ File dest = fileUtils.resolveFile(toDir, srcFiles[j]);
+ //Make sure parent dirs exist, else, create them.
+ try {
+ File destDir = new File(dest.getParent());
+ if (!destDir.exists()) {
+ destDir.mkdirs();
+ }
+ } catch (Exception e) {
+ log("Exception occured while trying to check/create "
+ + " parent directory. " + e.getMessage(),
+ Project.MSG_DEBUG);
+ }
+ destLastModified = dest.lastModified();
+ srcLastModified = new File(srcFiles[i]).lastModified();
+ //Check to see if dest file has to be recreated
+ if (forceOverwrite
+ || destLastModified < srcLastModified
+ || destLastModified < bundleLastModified[0]
+ || destLastModified < bundleLastModified[1]
+ || destLastModified < bundleLastModified[2]
+ || destLastModified < bundleLastModified[3]
+ || destLastModified < bundleLastModified[4]
+ || destLastModified < bundleLastModified[5]
+ || destLastModified < bundleLastModified[6]) {
+ log("Processing " + srcFiles[j],
+ Project.MSG_DEBUG);
+ FileOutputStream fos = new FileOutputStream(dest);
+ BufferedWriter out = new BufferedWriter(
+ new OutputStreamWriter(fos,
+ destEncoding));
+ FileInputStream fis = new FileInputStream(srcFiles[j]);
+ BufferedReader in = new BufferedReader(
+ new InputStreamReader(fis,
+ srcEncoding));
+ String line;
+ while((line = in.readLine()) != null) {
+ StringBuffer newline = new StringBuffer(line);
+ int startIndex = -1;
+ int endIndex = -1;
+ outer: while (true) {
+ startIndex = line.indexOf(startToken, endIndex + 1);
+ if (startIndex < 0 ||
+ startIndex + 1 >= line.length()) {
+ break;
+ }
+ endIndex = line.indexOf(endToken, startIndex + 1);
+ if (endIndex < 0) {
+ break;
+ }
+ String matches = line.substring(startIndex + 1,
+ endIndex);
+ //If there is a white space or = or :, then
+ //it isn't to be treated as a valid key.
+ for (int k = 0; k < matches.length(); k++) {
+ char c = matches.charAt(k);
+ if (c == ':' ||
+ c == '=' ||
+ Character.isSpaceChar(c)) {
+ endIndex = endIndex - 1;
+ continue outer;
+ }
+ }
+ String replace = null;
+ replace = (String) resourceMap.get(matches);
+ //If the key hasn't been loaded into resourceMap,
+ //use the key itself as the value also.
+ if (replace == null) {
+ log("Warning: The key: " + matches
+ + " hasn't been defined.",
+ Project.MSG_DEBUG);
+ replace = matches;
+ }
+ line = line.substring(0, startIndex)
+ + replace
+ + line.substring(endIndex + 1);
+ endIndex = startIndex + replace.length() + 1;
+ if (endIndex + 1 >= line.length()) {
+ break;
+ }
+ }
+ out.write(line);
+ out.newLine();
+ }
+ if(in != null) {
+ in.close();
+ }
+ if(out != null) {
+ out.close();
+ }
+ } else {
+ log("Skipping " + srcFiles[j] +
+ " as destination file is up to date",
+ Project.MSG_VERBOSE);
+ }
+ } catch (IOException ioe) {
+ throw new BuildException(ioe.getMessage(), location);
+ }
+ }
+ }
+ }
+}