org.enhydra.xml.xmlc.taskdef
Class L10nTask

org.enhydra.xml.xmlc.taskdef.L10nTask

public class L10nTask

This Ant task reads base, non-localized, template files, along with associated L10n property resource bundle files, and generates localized templates using text substitution. Applying a bit of a twist to standard resource bundle usage, values from base property resource bundle files are used as keys to match text for replacement in the base template file. Then, the key associated with the matched value is noted and used to look up the localized value from the resource bundle. At that point, the text is replaced and written to disk using the base template file name, the localized resource bundle locale string, and the base template file extension as the name of the file, e.g., foo.html, foo.properties, and foo_en_US.properties results in foo_en_US.html.

Existing localized template files are skipped as candidates for base template files even when matched by MatchingTask includes. Likewise, Generation of localized template files is avoided when they already exist and are newer than corresponding base template files and property resource bundle files. This can be overridden by setting setForce(boolean) to true.

Server Side Includes are also supported, allowing mockups to use includes, as always, with resulting localized templates having included content localized right along with content in the immediate template. Localized templates will actually no longer have includes, as they will all have been resolved, just as XMLC does at runtime. Setting the ssibase is supported, just as it is in XMLC.

Perl5.6, Java 1.3+ compatible, Regex Text Substitution Capabilities Provided By JRegex

This task provides much more than basic String matching. Strings provided by base property resource bundle values are used to match text in templates in a sophisticated manner. Base bundle values are pre-processed, making them literal regex patterns; e.g., "foo (bar)" becomes "foo\s+\(bar\)" which will match the target text regardless of whitespace within it in the template. Also notice that regex meta characters are escaped, ensuring all characters in the string are treated literally (other than the added \s+ between words to match any number of spaces, including line breaks). The only caveat is that line breaks in the target text of original template are lost upon replacement, though non-localized text remains unaffected.

Of course, there are some cases where one might want text replaced in one part of the document, but not another. For instance, attributes often contain meaningful values such as Ids and class names for CSS/Javascript or form data. It was for this purpose that the <skipReplace/> feature was added. This feature allows users significant control over what text gets localized and what doesn't. Regular expressions are used to match contexts where L10n should be avoided. But because regular expressions can be tricky to write, and since skipping attribute L10n is such a common use-case, <skipReplace idref="markup.attrs.all"/>, <skipReplace idref="markup.attrs.except" idrefSubPrefix="[optional pipe-delimited attribute names with no spaces here, defaults to [X]HTML spec defined %Text; attributes]"/>, and <skipReplace idref="markup.attrs" idrefSubPrefix="[required pipe-delimited attribute names with no spaces here]"/> have been pre-defined for convenience. For more info, see usage samples below as well as <skipReplace/> documentation.

Designing Property Resource Bundles

Property resource bundle files are written as usual, with special care being taken to match values in base bundle files with words/phrases in base template files. Localized bundle files need only match keys to the base bundle files, as the values will serve as the replacement text. To prevent a brittle template localization infrastructure, it is important to understand why even though XMLC provides fallback to the base template, a localized bundle should be defined even for the base bundle locale.

Because of special base bundle processing, it is highly recommended to set the baseBundleLocale to the Locale string representing the Locale of the base bundle property values. Without this, the system default Locale will be used, making the build environment specific. This is especially important when setting autoGenLocalizedBaseBundle to true. Also, note that property resource bundle files must use the ".properties" file extension, per ResourceBundle and the conventions of this task.

Normally, resource bundle encoding must be "ISO-8859-1". However, since Java6, it is possible to write resource bundles using any encoding supported by Java. To take advantage of this feature, this task must be run under Java6 or greater with bundleEncoding set to the desired encoding. This setting is ignored when running under JVMs older than Java6. Java6+ capabilities are reflected upon to allow for building/running under Java1.3 and greater, while providing access to features provided only in Java6+.

By default, property resource bundle file names are inferred using the base template file name (less extension) and presumed to be located in the same directory as the base template file (unless the bundleDir attribute is configured and set to a different directory than srcDir). While this behavior is fairly intuitive, it does not account for reuse of a single set of bundle files across multiple base template files. To accomplish this, use the masterBundle attribute to point to a master base property resource bundle file. Template associated bundle files will also be used and override values in the master bundle. This behavior can be modified using two options. First, using setMasterBundleDominant(true). ensures that properties found in the master bundle override properties in template associated bundles. Second, using setMasterBundleAppliesToAll(true) will make it so that the master bundle applies to all base templates, whether or not template associated bundles exist.

Example Usage

 <taskdef name="l10n"
             classname="org.enhydra.xml.xmlc.taskdef.L10nTask"
             classpathref="project.classpath"/>
 
 <l10n srcdir="${templates.dir}" includes="*.*ml"/>
 
 <l10n srcdir="${templates.dir}"
          includes="*.*ml"
          destdir="${dest.dir}"
          bundleDir="${bundle.dir}"
          bundleDirFlattened="true"
          masterBundle="{some.dir}/master.properties"
          masterBundleDominant="true"
          masterBundleAppliesToAll="false"
          ssi="true"
          ssiBase="[path to some directory, no need to set if SSI is already relative to including markup file]"
          verbose="true"
          force="false"
          defaultTemplateEncoding="UTF-8"
          bundleEncoding="ISO-8859-1" <!-- May use "UTF-8" if building/running under Java6+, negating the need for the native2ascii tool -->
          baseBundleLocale="en_US"
          autoGenLocalizedBaseBundle="true">
   <skipReplace description="skip replacement of target text inside attributes, except for those defined here"
                  idref="markup.attrs.except"
                  idRefSubPrefix="abbr|alt|label|prompt|standby|summary|title|value"/>
   <!--skipReplace description="skip replacement of target text inside all attributes, no exceptions"
                idref="markup.attrs.all"/-->
   <!--skipReplace description="skip replacement of target text inside those attributes defined here"
                idref="markup.attrs"
                idrefSubPrefix="class|id|name"/-->
   <!--skipReplace description="[some description of what your custom regex prefix/suffix does]"
                prefix="[some custom regex prefix to the target text]"
                suffix="[some custom regex suffix to the target text]"/-->
 </l10n>
 

Acknowledgment

This task is inspired by the BarracudaMVC Localize task. Many thanks go to the Localize task developers for coming up with this unique localization approach, which, in concert with XMLC, enables mockups to be deployed as live dynamic pages as is (other than optional automated pre-processing, such as performed by this task and XMLC), maintaining designer/developer separation of concerns.

While the general flow for discovering and generating templates is maintained, the details are nearly 100% rewritten from scratch. This task supports many more features and avoids a number of bugs and deficiencies inherent in the Localize task. As such, it is recommended that Localize users update their build scripts to use this new task. Enjoy!


Inner Class Summary
static class L10nTask.EmbededSkipReplaceType
           
 
Field Summary
protected  boolean autoGenLocalizedBaseBundle
           
protected  Locale baseBundleLocale
           
protected  File bundleDir
           
protected  boolean bundleDirFlattened
           
protected  String bundleEncoding
           
protected  File destDir
           
protected  boolean force
           
(package private) static String GRP_baseName
           
(package private) static String GRP_extension
           
(package private) static String GRP_l10nPattern
           
(package private) static String GRP_path
           
(package private) static String GRP_pathBaseName
           
(package private) static String GRP_pathBaseNameL10nPattern
           
(package private) static boolean IS_JAVA6_PLUS
           
protected  int logLevel
           
protected  boolean masterBundleAppliesToAll
           
protected  boolean masterBundleDominant
           
protected  File masterBundleFile
           
(package private) static jregex.Pattern RE_LENIENT_PATH_GROUPER
           
protected  File srcDir
           
protected  boolean ssi
           
protected  String ssiBase
           
protected  boolean verbose
           
 
Constructor Summary
L10nTask()
           
 
Method Summary
(package private) static void ()
           
 void addConfiguredSkipReplace(L10nTask.EmbededSkipReplaceType skipReplace)
          Optional - zero or more...
(package private) static InputSource buildInputSource(File file)
           
(package private) static String emptyIfNull(String str)
           
 void execute()
          Executes the task
(package private) static String loadTemplate(InputSource inputSource, boolean ssi, String ssiBase)
           
 void setAutoGenLocalizedBaseBundle(boolean autoGenLocalizedBaseBundle)
          Optional - a convenience to reduce the tedium of generating locale-named copies of the base bundle manually.
 void setBaseBundleLocale(String baseBundleLocaleStr)
          Optional - Note: because the default is the system default Locale, neglecting to set this means the build is platform specific.
 void setBundleDir(File bundleDir)
          Optional - It's only useful to define this if template associated bundle files are stored in a separate directory from the template files.
 void setBundleDirFlattened(boolean bundleDirFlattened)
          Optional - Only applicable when the bundleDir is set to a location other than the srcDir, otherwise ignored.
 void setBundleEncoding(String bundleEncoding)
          Optional
 void setDefaultTemplateEncoding(String defaultTemplateEncoding)
          Optional
 void setDestDir(File destDir)
          Optional
 void setForce(boolean force)
          Optional
 void setMasterBundle(File masterBundle)
          Optional - Localized master bundle files are presumed to be located in the same directory as the base master bundle.
 void setMasterBundleAppliesToAll(boolean masterBundleAppliesToAll)
          Optional - only relevant if a master bundle has been defined
 void setMasterBundleDominant(boolean masterBundleDominant)
          Optional - only relevant if a master bundle has been defined
 void setSrcDir(File srcDir)
          Required - This is the one and only required attribute.
 void setSsi(boolean ssi)
          Optional - This means that localized templates won't have Server Side includes to parse at runtime, as they will have been pre-resolved at build time.
 void setSsiBase(String ssiBase)
          Optional
 void setVerbose(boolean verbose)
          Optional
 

Field Detail

IS_JAVA6_PLUS

static final boolean IS_JAVA6_PLUS

GRP_pathBaseNameL10nPattern

static final String GRP_pathBaseNameL10nPattern

GRP_pathBaseName

static final String GRP_pathBaseName

GRP_path

static final String GRP_path

GRP_baseName

static final String GRP_baseName

GRP_l10nPattern

static final String GRP_l10nPattern

GRP_extension

static final String GRP_extension

RE_LENIENT_PATH_GROUPER

static final jregex.Pattern RE_LENIENT_PATH_GROUPER

masterBundleFile

protected File masterBundleFile

srcDir

protected File srcDir

destDir

protected File destDir

bundleDir

protected File bundleDir

logLevel

protected int logLevel

masterBundleDominant

protected boolean masterBundleDominant

masterBundleAppliesToAll

protected boolean masterBundleAppliesToAll

bundleDirFlattened

protected boolean bundleDirFlattened

autoGenLocalizedBaseBundle

protected boolean autoGenLocalizedBaseBundle

ssi

protected boolean ssi

verbose

protected boolean verbose

force

protected boolean force

ssiBase

protected String ssiBase

bundleEncoding

protected String bundleEncoding

baseBundleLocale

protected Locale baseBundleLocale
Constructor Detail

L10nTask

public L10nTask()
Method Detail

static void ()

setSrcDir

public void setSrcDir(File srcDir)
Required - This is the one and only required attribute. The srcDir must be both configured and exist or the task will fail fast.
Parameters:
srcDir - the directory containing the template files

setDestDir

public void setDestDir(File destDir)
Optional
Parameters:
destDir - the directory into which the localized template files are to be written, defaults to srcDir if not defined

setBundleDir

public void setBundleDir(File bundleDir)
Optional - It's only useful to define this if template associated bundle files are stored in a separate directory from the template files. If defined, and different from srcDir, the relative path of the template files from srcDir is hierarchy in which bundles in the bundleDir must be stored... except when using setBundleDirFlattened(true), in which case the bundleDir is flattened so that all bundles are searched for in the root of bundleDir.

Keep in mind that phrases common to all templates are better placed in a master bundle.

Parameters:
bundleDir - the directory containing the template file associated property resource bundle files, e.g, foo.html would have a foo.properties base bundle and one or more localized bundles, such as foo_en_US.properties, default is srcDir if not defined
See Also:
setBundleDirFlattened(boolean), setMasterBundle(java.io.File)

setMasterBundle

public void setMasterBundle(File masterBundle)
Optional - Localized master bundle files are presumed to be located in the same directory as the base master bundle.
Parameters:
masterBundle - the base master L10n properties file
See Also:
setMasterBundleDominant(boolean), setMasterBundleAppliesToAll(boolean)

setMasterBundleDominant

public void setMasterBundleDominant(boolean masterBundleDominant)
Optional - only relevant if a master bundle has been defined
Parameters:
masterBundleDominant - true to make the master bundle dominant to, or override, corresponding template associated bundles, default is false
See Also:
setMasterBundle(java.io.File)

setMasterBundleAppliesToAll

public void setMasterBundleAppliesToAll(boolean masterBundleAppliesToAll)
Optional - only relevant if a master bundle has been defined

Caution: if this is set to true includes must be carefully set, otherwise localized templates may be generated for more base templates than you may have planned on!

Parameters:
masterBundleAppliesToAll - true to make the master bundle apply to all templates, not just those having corresponding template associated bundles, default is false
See Also:
setMasterBundle(java.io.File)

setBundleDirFlattened

public void setBundleDirFlattened(boolean bundleDirFlattened)
Optional - Only applicable when the bundleDir is set to a location other than the srcDir, otherwise ignored. Note that if false, the bundleDir is presumed to store bundles in the same relative directory hierarchy as the relative paths to the corresponding base template files from srcDir. Setting bundleDirFlattened to true short-circuits this relative path interdependency, allowing changes to srcDir without requiring changes to the directory hierarchy within bundleDir. The caveat, however, is that base template files of the same name, in different directory hierarchies, will not be able to use distinct bundles, e.g., srcDir/index.html and srcDir/foo/index.html will necessarily share the same bundleDir/index.*properties bundle. Then again, this behavior may be desirable, as it reduces duplication... though this can be achieved even more effectively using a masterBundle.
Parameters:
bundleDirFlattened - true to treat bundlDir as a flattened directory with no hierarchy so all bundle files are presumed to exist in the root of bundleDir, default is false

setSsi

public void setSsi(boolean ssi)
Optional - This means that localized templates won't have Server Side includes to parse at runtime, as they will have been pre-resolved at build time. It also means that phrases in includes files will be localized in the template, where they wouldn't at runtime... unless, of course, one created includes template associated resource bundles or applied a master bundle and set masterBundleAppliesToAll(true).
Parameters:
ssi - true to enable parsing Server Side Includes, default is false
See Also:
setSsiBase(java.lang.String)

setSsiBase

public void setSsiBase(String ssiBase)
Optional
Parameters:
ssiBase - the base directory of the SSI files, only applicable when SSI parsing is enabled, may be null, in which case the base directory will be that of the including template
See Also:
setSsi(boolean)

setVerbose

public void setVerbose(boolean verbose)
Optional
Parameters:
verbose - true to see verbose output, default is false

setForce

public void setForce(boolean force)
Optional
Parameters:
force - true to ignore template and bundle timestamps, thus forcing generation of L10n templates, default is false

setBundleEncoding

public void setBundleEncoding(String bundleEncoding)
Optional
Parameters:
bundleEncoding - the encoding of the resource bundles, ignored unless running under Java version 1.6 or greater, default is "ISO-8859-1", as required by java.util.ResourceBundle prior to Java 1.6.

setBaseBundleLocale

public void setBaseBundleLocale(String baseBundleLocaleStr)
Optional - Note: because the default is the system default Locale, neglecting to set this means the build is platform specific. You've been warned! E.g...

"_en", "en", "en_", "en__", "_en__", "en_US", "en__US", "en_POSIX", "en__POSIX", "en___POSIX", "en_US_POSIX", "en__US__POSIX", "US", "US_", "_US", "_US_", "US_POSIX", "US__POSIX", "_US_POSIX", "_US__POSIX", "_POSIX", "__POSIX"

Parameters:
baseBundleLocaleStr - a String representing the Locale of the text in the base resource bundle (values rather than keys, actually), default is the current system default Locale
See Also:
setAutoGenLocalizedBaseBundle(boolean), I18nUtil.locale(String)

setAutoGenLocalizedBaseBundle

public void setAutoGenLocalizedBaseBundle(boolean autoGenLocalizedBaseBundle)
Optional - a convenience to reduce the tedium of generating locale-named copies of the base bundle manually. Works in concert with the base bundle locale setting. Having locale specific bundles for each locale, especially the base locale, frees up the base bundle to use alternate values for template keys - recall that base bundle values are actually the keys used to match strings in the base template. This ameliorates phrasing duplication among base template, base bundle, and locale-named bundle files.

For instance, a base bundle key/value pair may go from "key.1=hello citizens" to "key.1=key.1" and now the base template would contain "key.1" instead of "hello world". By the same token the base bundle and base template could continue to use "hello citizens" while the locale-named template for the base bundle locale may use "hello U.S. citizens" (assuming a base bundle locale of en_US). Either way, the localized phrase is now entirely driven by the locale-named bundle(s) and may vary from the base bundle and base template as much or as little as disired. Phrasing is centralized, thus avoiding a highly coupled, brittle, template localization infrastructure.

Note that when using this feature, it is recommended that your application code set both XMLCCreateOptions.setUserLocale(Locale) and XMLCCreateOptions.setFallbackLocale(Locale) (and/or setDefaultFallbackLocale(Locale)) when calling the create(XMLCCreateOptions) method. For instance, let's say the user locale is "fr", but no French translation has been created. Without specifying a fallback locale, the base template will serve as the default fallback. But if the base template uses keys like "key.1" instead of phrases like "hello citizens" (the benefits of which are detailed above), it won't make much sense to the user. Assuming we've set the base bundle locale to "en_US" and either manually or auto-generated the localized base bundle, by setting the current feature to true, then we can set Locale.US as the fallback locale and the en_US template will be used as fallback instead of the base template. In effect, your preferred localized template acts as the defacto base template. Of course if neither the user locale template nor the preferred fallback locale template happen to exist, the base template continues to serve as the final fallback.

Parameters:
autoGenLocalizedBaseBundle - true to generate a copy of the base bundle named in accordance with the base bundle locale, but only if it doesn't exist already, applies to both template and master bundles, default is false
See Also:
setBaseBundleLocale(java.lang.String)

setDefaultTemplateEncoding

public void setDefaultTemplateEncoding(String defaultTemplateEncoding)
Optional
Parameters:
defaultTemplateEncoding - the encoding to use to read/write template files if none can be auto-detected, default is "UTF-8"

addConfiguredSkipReplace

public void addConfiguredSkipReplace(L10nTask.EmbededSkipReplaceType skipReplace)
Optional - zero or more...
 <skipReplace
     description="[some description]"
     prefix="[some regex prefix]"
     suffix="[some regex suffix]"/>
 --OR--
 <skipReplace
     description="[some description]"
     idref="[some predefined id reference]"
     idrefSubPrefix="[some predefined regex sub prefix]"
     idrefSubSuffix="[some predefined regex sub suffix]"/>
 

The prefix/suffix combinations are applied as the first and third group of a regular expression, i.e., "(skipReplace.prefix)(target text)(skipReplace.suffix)". The prefix and suffix are used to search for instances of the target text surrounded by, or embedded within, the defined pattern. In these cases L10n template replacement that would otherwise take place is skipped. In other words, matched patterns are flagged for skipping replacement (with the flag removed prior to final output of the localized template). When defining more than one skipReplace element, keep in mind that matches trump non-matches.

Any regular expression may be used, though caution will need to be taken when using parenthesis. If you need to use them, make sure to define them in such a way that they won't define a group, e.g., (?:regex). All bets are off if you define a group because the three predefined groups are referenced in the replacement. This is a power feature. And with power comes responsibility. How well this feature works is dependent on how well you write your regular expressions. Using available predefined skipReplace elements will help ensure that the regular expressions work, as they have been well tested.

The idref attribute is used to reference a predefined skipReplace element, which alleviates most (sometimes all) of the need to write, potentially, complex regular expressions. When idref is defined, user-defined prefix and suffix attributes are ignored. The attributes idrefSubPrefix and idrefSubSuffix, if utilized by the respective predefined skipReplace elements, may have defaults that can be overridden. When overriding, use patterns documented by the respective predefined skipReplace element. The following predefined elements may be used...

idref="markup.attrs.all"
Skips all cases where the target text exists within attributes
All other skipReplace attributes are ignored
Example: <skipReplace idref="markup.attrs.all"/>
idref="markup.attrs.except"
Skips all cases where the target text exists within attributes, **except** those defined in idrefSubPrefix
Optionally provide idrefSubPrefix to override defaults (user-defined idrefSubSuffix is ignored) - expects pipe-delimited attribute names with no spaces
Default is idrefSubPrefix="abbr|alt|label|prompt|standby|summary|title" ([X]HTML spec defined %Text; attributes)
Examples: <skipReplace idref="markup.attrs.except" idrefSubPrefix="alt|title|value"/>, or simply <skipReplace idref="markup.attrs.except"/>
idref="markup.attrs"
Skips all cases where the target text exists within user-defined attributes
Must provide idrefSubPrefix (no default, user-defined idrefSubSuffix is ignored) - expects pipe-delimited attribute names with no spaces
Example: <skipReplace idref="markup.attrs" idrefSubPrefix="class|id|name"/>

Note that for the predefined markup.attrs* elements, attributes are recognized with or without namespaces. So, even though, e.g., "id" is defined by the user without a namespace, both "id" and "[somePrefix]:id" are matched.

Parameters:
skipReplace - a configured <skipReplace/> element
Throws:
BuildException - if the defined skipReplace is improperly configured, meaning both prefix and suffix are empty, or undefined, and no idref has been provided or an idref is provided and doesn't match a predefined id or idref is provided and matches a predefined id, but a required idrefSubPrefix/Suffix is not provided

execute

public void execute()
Executes the task
Throws:
BuildException - if the build is improperly configured or enters a bad state where it cannot proceed

emptyIfNull

static String emptyIfNull(String str)

buildInputSource

static InputSource buildInputSource(File file)
                             throws IOException

loadTemplate

static String loadTemplate(InputSource inputSource,
                           boolean ssi,
                           String ssiBase)
                    throws IOException


Copyright © 1999-2002 David Li, enhydra.org. All Rights reserved.