|
|||||||||
PREV CLASS NEXT CLASS | FRAMES NO FRAMES | ||||||||
SUMMARY: INNER | FIELD | CONSTR | METHOD | DETAIL: FIELD | CONSTR | METHOD |
org.enhydra.xml.xmlc.taskdef.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.
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
.
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.
<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>
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 |
static final boolean IS_JAVA6_PLUS
static final String GRP_pathBaseNameL10nPattern
static final String GRP_pathBaseName
static final String GRP_path
static final String GRP_baseName
static final String GRP_l10nPattern
static final String GRP_extension
static final jregex.Pattern RE_LENIENT_PATH_GROUPER
protected File masterBundleFile
protected File srcDir
protected File destDir
protected File bundleDir
protected int logLevel
protected boolean masterBundleDominant
protected boolean masterBundleAppliesToAll
protected boolean bundleDirFlattened
protected boolean autoGenLocalizedBaseBundle
protected boolean ssi
protected boolean verbose
protected boolean force
protected String ssiBase
protected String bundleEncoding
protected Locale baseBundleLocale
Constructor Detail |
public L10nTask()
Method Detail |
static void()
public void setSrcDir(File srcDir)
srcDir
- the directory containing the template filespublic void setDestDir(File destDir)
destDir
- the directory into which the localized template files are
to be written, defaults to srcDir if not definedpublic void setBundleDir(File bundleDir)
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
.
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 definedsetBundleDirFlattened(boolean)
,
setMasterBundle(java.io.File)
public void setMasterBundle(File masterBundle)
masterBundle
- the base master L10n properties filesetMasterBundleDominant(boolean)
,
setMasterBundleAppliesToAll(boolean)
public void setMasterBundleDominant(boolean masterBundleDominant)
masterBundleDominant
- true to make the master bundle dominant to,
or override, corresponding template associated
bundles, default is falsesetMasterBundle(java.io.File)
public void setMasterBundleAppliesToAll(boolean masterBundleAppliesToAll)
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!
masterBundleAppliesToAll
- true to make the master bundle apply to
all templates, not just those having
corresponding template associated bundles,
default is falsesetMasterBundle(java.io.File)
public void setBundleDirFlattened(boolean bundleDirFlattened)
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
.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 falsepublic void setSsi(boolean ssi)
masterBundleAppliesToAll(true)
.ssi
- true to enable parsing Server Side Includes, default is falsesetSsiBase(java.lang.String)
public void setSsiBase(String ssiBase)
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 templatesetSsi(boolean)
public void setVerbose(boolean verbose)
verbose
- true to see verbose output, default is falsepublic void setForce(boolean force)
force
- true to ignore template and bundle timestamps, thus forcing
generation of L10n templates, default is falsepublic void setBundleEncoding(String bundleEncoding)
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.public void setBaseBundleLocale(String baseBundleLocaleStr)
"_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"
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 LocalesetAutoGenLocalizedBaseBundle(boolean)
,
I18nUtil.locale(String)
public void setAutoGenLocalizedBaseBundle(boolean autoGenLocalizedBaseBundle)
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.
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 falsesetBaseBundleLocale(java.lang.String)
public void setDefaultTemplateEncoding(String defaultTemplateEncoding)
defaultTemplateEncoding
- the encoding to use to read/write template
files if none can be auto-detected, default
is "UTF-8"public void addConfiguredSkipReplace(L10nTask.EmbededSkipReplaceType skipReplace)
<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"
skipReplace
attributes are ignored<skipReplace idref="markup.attrs.all"/>
idref="markup.attrs.except"
idrefSubPrefix
idrefSubPrefix
to override defaults (user-defined idrefSubSuffix
is ignored) - expects pipe-delimited attribute names with no spacesidrefSubPrefix="abbr|alt|label|prompt|standby|summary|title"
([X]HTML spec defined %Text; attributes)<skipReplace idref="markup.attrs.except" idrefSubPrefix="alt|title|value"/>
, or simply <skipReplace idref="markup.attrs.except"/>
idref="markup.attrs"
idrefSubPrefix
(no default, user-defined idrefSubSuffix
is ignored) - expects pipe-delimited attribute names with no spaces<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.
skipReplace
- a configured <skipReplace/> elementBuildException
- 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 providedpublic void execute()
BuildException
- if the build is improperly configured or enters a
bad state where it cannot proceedstatic String emptyIfNull(String str)
static InputSource buildInputSource(File file) throws IOException
static String loadTemplate(InputSource inputSource, boolean ssi, String ssiBase) throws IOException
|
|||||||||
PREV CLASS NEXT CLASS | FRAMES NO FRAMES | ||||||||
SUMMARY: INNER | FIELD | CONSTR | METHOD | DETAIL: FIELD | CONSTR | METHOD |