3
votes

I am using bean validation and gettext for i18n. How can I mark the message string for translation, so it gets extracted with xgettext? For example

@NotNull(message="Please enter a valid string")
String string;

Normall I call i18n.tr, but how to mark a constant?

Kind regards Christian

Edit: At runtime I am using a custom message interpolator for translation.

3
I have a github project (github.com/jhorstmann/i18n) that extracts messages from the java bytecode rather than the source, it shouldn't be too difficult to modify it to consider annotation values. Let me know if this would a useful addition.Jörn Horstmann
This is surely a good library. If it is possible to consider annotation values this should be done for completeness. Maybe only annotationvalues with curly-braces around? Also a GettextMessageInterpolator should be added for JSR303-compatibility.Christian

3 Answers

1
votes

You could try to build a custom MessageInterpolator which delegates to gettext. In case you're working with Hibernate Validator, it might make sense to derive your interpolator implementation from ResourceBundleMessageInterpolator to re-use the actual interpolation logic.

That said, I'd be very interested in the outcome of this. Maybe you could share the approach you're finally taking? I could imagine this to be interesting for others as well.

1
votes

I am normally not answering my own questions. But for now I came up with following solution:

I am marking my strings as follows in an additional comment (I know not DRY anymore):

//_.trans("Please enter a valid string");
@NotNull(message="Please enter a valid string")
String string;

I am calling following script in my pom:

#!/bin/bash

# $1 -> java source directory
# $2 -> output file
# $3 -> po directory

echo "Source Directory: $1"
echo "Keys File: $2"
echo "PO Directory: $3"

xgettext --from-code utf-8 -L Java --force-po -ktrc:1c,2 -ktrnc:1c,2,3 -ktr -kmarktr -ktrn:1,2 -k -o "$2" $(find "$1" -name "*.java")
sed "s/\/\/_/_/g" $(find "$1" -name "*.java") | xgettext -F --from-code utf-8 -L Java -ktrans -k -j -o "$2" -

pofiles=$3/*.po
shopt -s nullglob
for i in $pofiles
do
   echo "msgmerge $i"
   msgmerge --backup=numbered -U $i $2
done

This script first calls xgettext normally and then calls sed to remove the comment slashes and pipes to xgettext. Thus I have all my keys in keys.pot.

pom.xml - profile:

    <profile>
        <id>translate</id>
        <build>
            <plugins>
                <plugin>
                    <artifactId>exec-maven-plugin</artifactId>
                    <groupId>org.codehaus.mojo</groupId>
                    <version>1.2.1</version>
                    <executions>
                        <execution>
                            <id>xgettext</id>
                            <phase>generate-resources</phase>
                            <goals>
                                <goal>exec</goal>
                            </goals>
                            <configuration>
                                <executable>sh</executable>
                                <arguments>
                                    <argument>${project.basedir}/extractkeys.sh</argument>
                                    <argument>src/main/java</argument>
                                    <argument>src/main/resources/po/keys.pot</argument>
                                    <argument>src/main/resources/po</argument>
                                </arguments>
                                <workingDirectory>${project.basedir}</workingDirectory>
                            </configuration>
                        </execution>
                    </executions>
                </plugin>
                <plugin>
                    <groupId>org.xnap.commons</groupId>
                    <artifactId>maven-gettext-plugin</artifactId>
                    <version>1.2.3</version>
                    <configuration>
                        <keysFile>${project.basedir}/src/main/resources/po/keys.pot</keysFile>
                        <outputDirectory>${project.basedir}/src/main/resources</outputDirectory>
                        <outputFormat>properties</outputFormat>
                        <poDirectory>${project.basedir}/src/main/resources/po</poDirectory>
                        <sourceDirectory>${project.build.sourceDirectory}/ch/sympany/tourist</sourceDirectory>
                        <sourceLocale>en</sourceLocale>
                        <targetBundle>${project.groupId}.Messages</targetBundle>
                    </configuration>
                    <executions>
                        <execution>
                            <goals>
                                <goal>dist</goal>
                            </goals>
                            <phase>generate-resources</phase>
                        </execution>
                    </executions>
                </plugin>
            </plugins>
        </build>
    </profile>

I know the build is not platform independent anymore but in a separate profile I can live with it. However, it works also on cygwin for the windows guys.

My messageinterpolator is as follows:

public class GettextMessageInterpolator implements MessageInterpolator {

    private final MessageInterpolator delegate;

    public GettextMessageInterpolator() {
        this.delegate = new ResourceBundleMessageInterpolator();
    }

    @Override
    public String interpolate(String message, Context context) {
        return this.interpolate(message, context, ClientLocalLocator.get());
    }

    @Override
    public String interpolate(String message, Context context, Locale locale) {   
        I18n i18n = ClientLocalLocator.getI18n();
        String retVal = i18n.tr(message);
        if (StringUtils.isNotBlank(retVal))
            return retVal;
        return delegate.interpolate(message, context, locale);
    }

}
0
votes

I am not sure about the gettext integration with Java. Maybe you can explain more how it works.

From a Bean Validation point of view i18n is handled via resource files. Instead of adding the message directly to the code, you would do:

@NotNull(message="{my.notNull.message}")
String string;

You then define your messages in ValidationMessages.properties and its language specific counter parts. Not sure how gettext would fit into the picture here.

Edit:

In case you really want to use xgettext I see the problem in that gettext looks for tokens of the form gettext("whatever"). The you can do via xgettext is to specify a different keyword for gettext via the -k option. That won't help in this case though. If you doing all this via the command line I could imagine that you use sed to preparse the input for xgettext. Something like:

find . -name "*.java" | xargs sed -e 's/message="\(.*\)"/gettext("\1")/' | xgettext 

Something like this.