Linguaj provides a simple way to use gettext to translate user visible strings in Java code.
Linguaj is licensed under the Apache v2.0 license.
Linguaj makes use of gettext string extraction tool (xgettext) and gettext .po file format, but does not use gettext Java support. Instead it provides a tool named po-compile to turn .po files into .java files, which can then be used with the provided Translator Java class.
You need to have gettext installed, or at least access to the xgettext tool.
One of Linguaj components (scripts/po-compile) is written in Python and requires the jinja2 and polib modules. Those can be installed with:
pip install -r requirements.txt
If you are running Linux, they should be available as packages as well.
Either create a ./gradlew build
, or add a reference in your project to the
:linguaj
folder from this repository.
Add this import line to each Java class which contains text to be translated:
import static com.greenyetilab.linguaj.Translator.tr;
Then wrap translatable text in tr()
calls. This line:
System.out.println("Hello");
Becomes:
System.out.println(tr("Hello"));
Gettext provides the xgettext tool to extract translatable texts from the source code. To get it to work with Linguaj, invoke it like this:
xgettext --keyword=tr --keyword=trn:1,2 -o po/messages.pot *.java
This will go through all .java files in the current folder and create a file named messages.pot in the po folder. The messages.pot file is a "message template". It is a text file which contains all the translatable texts.
Create a .po for your language. For example to translate your texts to French, either copy messages.pot to fr.po and adjust the header fields, or run:
msginit -i messages.pot -l fr -o fr.po
You can now translate strings in fr.po, using either your text editor or, preferably, a tool such as poedit or lokalize.
Once your .po files are ready, compile them into .java files using the scripts in the scripts/ folder. The easiest way is to run:
scripts/po-compile-all <path/to/po/dir> <where/to/generate/java/files>
Make sure the folder where the generated .java files is in the class path. Now run your code again, your texts should be translated.
One of the benefits of gettext is advanced plural handling. To translate a plural string, use trn() instead of tr(). It works like this:
import static com.greenyetilab.linguaj.Translator.trn;
...
class Backup {
...
void work(String[] files) {
...
System.out.println(trn("%# file saved", "%# files saved", files.length));
}
}
The syntax is trn("singular form", "plural form", number)
. Depending on the
value of number and of the current locale, trn() will return the appropriate
string. All occurrences of "%#" in the string will be replaced with number
.
This is much better than using code like that:
if (files.length == 1) {
System.out.println(tr("1 file saved"));
} else {
System.out.println(tr("%d files saved", files.length));
}
Because that code fails for languages with different grammar rules for plural.
For example French does not use an 's' when number
is 0, Polish uses one
plural form when number
is between 2 and 4, and another form when it is
between 5 and 9.
Linguaj comes with an example which demonstrates these different plural forms.
You can try it by running ant example-run
.
Linguaj API supports string formatting, based on String.format(). This means that instead of writing this:
String msg = String.format(tr("Hello %s, how are you?"), name);
You can write:
String msg = tr("Hello %s, how are you?", name);
This works with trn() as well.
By default, Linguaj will load the most appropriate translation based on the
current locale, but you can force it to use a different locale by calling
init()
with an argument, like this:
import com.greenyetilab.linguaj.Translator;
...
// Let's speak French
Translator.init("fr");
...
// Now switch to Brazilian Portuguese. If there is no Brazilian Portuguese
// translation available, Linguaj falls back to Portuguese.
Translator.init("pt_BR");
When I realized I needed translations for my Burger Party Android game, I looked at existing solutions for Java. I was not happy with the Java way of using string constants: I find it nicer to work with English sentences than with string constants. I also wanted to have good plural support, something which I know is tricky.
Having past experience with gettext, I looked into using it. gettext is a widespread, robust tool. There are many tools to work with it. I saw it had Java support so I started using it, but bumped into a legal issue.
Java code can make use of gettext plural support, but to do so one has to use the libintl.jar library provided by gettext. This is where the issue appears: the code for libintl.jar is licensed under LGPL 2.1 or later. It is not clear whether it is legal to use LGPL code in a proprietary .apk file, because the .jar is not shipped as is in the .apk: it must be converted to work on the Android VM (Dalvik), which does not use the same byte-code as the JVM.
I am quite confident that the spirit of the LGPL would allow LGPL code to be used in a proprietary .apk, but I don't want to take a decision in the field of licensing based on gut feeling. I reached out to the gettext maintainers, who redirected me to the FSF lawyers, but the FSF lawyers ask for a $150 fee to answer any question which implies proprietary software. I am not even sure I am going to make that much money with my game, so I'd rather use Linguaj API for now. Linguaj is licensed under the Apache 2.0 license so it does not have that problem.
As I mentioned above, I am using Linguaj in Burger Party. It would be great to hear from you if you use Linguaj in your projects!