Skip to content

Commit

Permalink
tagging release 1.5
Browse files Browse the repository at this point in the history
  • Loading branch information
Nikita Koval committed Aug 9, 2017
1 parent 9f196dc commit 2b1178f
Show file tree
Hide file tree
Showing 8 changed files with 272 additions and 111 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@

# CHANGELOG

## 1.5 -
- NEW: Implemented ability to provide language from the outside.
- NEW: Supported append mode of creating default properties files.
- FIX: Enums corruption because of LOCALE_U initialization on the first line.
- FIX: Escape backslashes in a property file.

## 1.4 - 2017-05-23

- FIX: Append properties to a file in natural order
Expand Down
174 changes: 132 additions & 42 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,77 +1,167 @@
UI Localizer
============

Download
--------
<a href='https://bintray.com/devexperts/Maven/uilocalizer/_latestVersion'><img src='https://api.bintray.com/packages/devexperts/Maven/uilocalizer/images/download.svg'></a>.
<a href='https://bintray.com/devexperts/Maven/uilocalizer/_latestVersion'><img src='https://api.bintray.com/packages/devexperts/Maven/uilocalizer/images/download.svg'></a>

What is it?
-----------
UI Localizer — tool for simplifying localization of Java applications. It's just an annotation processor that captures classes annotated with @Localizable in the compile-time. It performs two actions:
UI Localizer — tool for simplifying localization of Java applications. It's just an annotation processor that captures classes annotated with `@Localizable` in the compile-time. It performs two actions:

1. Through bytecode manipulations, replaces initialization of @Localizable strings by reading value from .propreties file (using ResourceBundle)
1. Through bytecode manipulations, replaces initialization of `@Localizable` strings by reading value from .propreties file (using `ResourceBundle`)
2. Generates .properties template file with default values from source code

How to use tool in Java Project
-------------------------------
You should just add uilocalizer.jar to compiler classpath and mark all localizable strings with @Localizable. Example:
You should just add uilocalizer.jar to compiler classpath and mark all localizable strings with `@Localizable`. Example:

@Localizable("order.dialog.title") private static final String TITLE = "Order Details";
@Localizable("order.dialog.comfirm") private static final String CONFIRM = "Confirm and Exit";
```java
@Localizable("order.dialog.title") private static final String TITLE = "Order Details";
@Localizable("order.dialog.comfirm") private static final String CONFIRM = "Confirm and Exit";
```

The only one annotation parameter should contain name of property file (before first dot) and property key (after first dot).
Compilation of the example above will result in generation of the following .properties template file:
The only one annotation parameter should contain a name of property file (before the first dot) and property key (after the first dot).
Compilation of the example above will generate the following .properties template file:

dialog.title=Order Details
dialog.confirm=Confirm and Exit
```ini
dialog.title=Order Details
dialog.confirm=Confirm and Exit
```

To add support of any language in your application, you should:
To add support for any language in your application, you should:

1. Take the generated template file
2. Translate all properties values into desired language
2. Translate all properties values into the desired language
3. Rename file as originalname_LANGUAGETAG.properties
4. Add resulting .properties file to the application runtime classpath

Example of resulting French translation file:

dialog.title=Détails de la Commande
dialog.confirm=Confirmer et Quitter

You can choose current language in your appilcation using java property -Dui.dialogs.locale. Syntax for the java property is the same as syntax for argument of java.util.Locale#forLanguageTag method:

-Dui.dialogs.locale=fr-fr
```ini
dialog.title=Détails de la Commande
dialog.confirm=Confirmer et Quitter
```

You can choose the current language in your application using java property `ui.dialogs.locale`. Syntax for the java property is the same as syntax for argument of [java.util.Locale.forLanguageTag](https://docs.oracle.com/javase/8/docs/api/java/util/Locale.html#forLanguageTag-java.lang.String-) method:

```bash
-Dui.dialogs.locale=fr-fr
```

UI Localizer tool is fail-safe. That means if anything of mentioned above is missing (java property, key in a file for concrete language or a file for the language itself), default string value from initial source code will be used.

If you use UI Localizer for compilation multiple modules and you use the same property file name you should use the option `com.devexperts.uilocalizer.appendToPropertyFile` set to `true`, properties will be appended to the end of the file, e.g.: `-Acom.devexperts.uilocalizer.propertyFileAppend=true`. The default value is `false`.
Use this option with caution, don't forget to clean up all template files before compilation with UI Localizer.

Language consistency
--------------------
There is a way to store the language properties in one place. This method will guarantee language consistency of your application.
Also using such a controller you can change language "on the fly" by setting language. To use it you should:

1. Create a class with `public static Locale getLanguage()` method. For example:

```java
public class SingletonLanguageController {
private static final Object lock = new Object();
private static volatile Locale lang;
private static final String LANG_PROP = "ui.dialogs.locale";
private static final String DEFAULT_LOCALE_TAG = "en-US";

private SingletonLanguageController() {
}

public static Locale getLanguage() {
if (lang == null) {
synchronized (lock) {
if (lang == null) {
lang = Locale.forLanguageTag(System.getProperty(LANG_PROP, DEFAULT_LOCALE_TAG));
}
}
}
return lang;
}

public static void updateLocale() {
synchronized (lock) {
lang = Locale.forLanguageTag(System.getProperty(LANG_PROP, DEFAULT_LOCALE_TAG));
}
}

public static void setLanguage(String localeTag) {
synchronized (lock) {
System.setProperty(LANG_PROP, localeTag);
updateLocale();
}
}
}
```

UI Localizer tool is fail-safe. That means if anything of mentioned above is missing (java property, key in file for concrete language or file for concrete language itself), default string value from initial source code will be used.
2. Pass an argument to an annotation processor with option name `com.devexperts.uilocalizer.languageControllerPath`, value - fully qualified name:

`-Acom.devexperts.uilocalizer.languageControllerPath=com.example.SingletonLanguageController`

Bytecode changes
----------------
Tool just replaces localizable string literals by invocation of static method, that attempts to read property with needed locale. In attempt fails, method returns initial value from the code. Example of annotated class and resulting bytecode:

public class TestClass {
@Localizable("scope.key")
private static final String KUKU = "cucumber";
}

public class TestClass {

public TestClass() {
super();
The tool just replaces each localizable string literal with a static method call, that attempts to read property with needed locale. If the attempt fails, the method returns initial value from the code. An example of annotated class and resulting bytecode:

**Initial code:**

```java
public class TestClass {
@Localizable("scope.key")
private static final String KUKU = "cucumber";
}
```

**Transformed code without language controller:**

```java
public class TestClass {

@Localizable(value = "scope.key")
private static final String KUKU = getString_u("scope.key", "cucumber");
private static volatile java.util.Locale LOCALE_u;

private static java.lang.String getString_u(String key, String defaultString) {
try {
if (LOCALE_u == null)
LOCALE_u = java.util.Locale.forLanguageTag(System.getProperty("ui.dialogs.locale", "en-US"));
return new String(
java.util.ResourceBundle.getBundle(key.substring(0, key.indexOf(46)), LOCALE_u)
.getString(key.substring(key.indexOf(46) + 1)));
} catch (Exception e) {
return defaultString;
}
@Localizable(value = "scope.key")
private static final String KUKU = getString_u("scope.key", "cucumber");
private static final java.util.Locale LOCALE_u = java.util.Locale.forLanguageTag(java.lang.System.getProperty("ui.dialogs.locale", "en-US"));

private static java.lang.String getString_u(java.lang.String key, java.lang.String defaultString) {
try {
java.lang.String val = java.util.ResourceBundle.getBundle(key.substring(0, key.indexOf(46)), LOCALE_u).getString(key.substring(key.indexOf(46) + 1));
return new java.lang.String(val.getBytes("ISO-8859-1"), "UTF-8");
} catch (java.lang.Exception e) {
return defaultString;
}
}
}
```

**Transformed code with language controller:**

```java
public class TestClass {

@Localizable(value = "scope.key")
private static final String KUKU = getString_u("scope.key", "cucumber");

private static String getString_u(String key, String defaultString) {
try {
return new String(
java.util.ResourceBundle.getBundle(key.substring(0, key.indexOf(46)), SingletonLanguageController.getLanguage())
.getString(key.substring(key.indexOf(46) + 1)));
} catch (Exception e) {
return defaultString;
}
}
}
```


Notes
-----

1. Smart build systems may omit part of classes during repeated build. To avoid missing properties in the template file, perform cleanup first.
2. Template files will be created in current directory of javac process. If you use IDEA, it's likely ~/.IntellijideaXX/system/compile-server.
3. You can put not only files with translations into runtime classpath, but template files too. It's not necessary, but allows you to change human readable strings dynamically, without project rebuild.
2. Template files will be created in the current directory of javac process. If you use IDEA, it's likely ~/.IntellijideaXX/system/compile-server.
3. You can put not only files with translations into runtime classpath, but template files too. It's not necessary but allows you to change human readable strings dynamically, without project rebuild.
21 changes: 15 additions & 6 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

<groupId>com.devexperts</groupId>
<artifactId>uilocalizer</artifactId>
<version>1.5-SNAPSHOT</version>
<version>1.5</version>
<name>UI Localizer</name>

<licenses>
Expand All @@ -20,11 +20,6 @@
<url>http://www.devexperts.com/</url>
</organization>

<url>https://github.com/Devexperts/egen</url>
<issueManagement>
<url>https://github.com/Devexperts/egen/issues</url>
</issueManagement>

<properties>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
Expand Down Expand Up @@ -205,6 +200,20 @@
</execution>
</executions>
</plugin>
<!-- Configuration for release -->
<plugin>
<groupId>com.devexperts.jgitflow</groupId>
<artifactId>jgitflow-maven-plugin</artifactId>
<version>1.0-m5.1-devexperts</version>
<configuration>
<autoVersionSubmodules>true</autoVersionSubmodules>
<flowInitContext>
<versionTagPrefix>uilocalizer-</versionTagPrefix>
</flowInitContext>
<squash>true</squash>
<scmCommentPrefix>[release] </scmCommentPrefix>
</configuration>
</plugin>
</plugins>
</build>
</profile>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,15 @@ class AddLocalizationNodesVisitor extends JCTree.Visitor {
private Collection<JCTree.JCVariableDecl> localizableVariableDeclarations;
private JCTree.JCClassDecl currentClass;
private Set<JCTree.JCClassDecl> localizedClasses;
private final String languageControllerPath;

AddLocalizationNodesVisitor(AstNodeFactory localizationNodesFactory,
Collection<JCTree.JCVariableDecl> localizableVariableDeclarations)
Collection<JCTree.JCVariableDecl> localizableVariableDeclarations,
String languageControllerPath)
{
this.localizationNodesFactory = localizationNodesFactory;
this.localizableVariableDeclarations = localizableVariableDeclarations;
this.languageControllerPath = languageControllerPath;
localizedClasses = new HashSet<>();
}

Expand All @@ -55,7 +58,8 @@ public void visitVarDef(JCTree.JCVariableDecl jcVariableDecl) {
if (!localizedClasses.contains(currentClass)) {
localizedClasses.add(currentClass);
localizationNodesFactory.setPositionFor(currentClass);
currentClass.defs = currentClass.defs.prepend(localizationNodesFactory.getLocaleConstantDecl());
if (languageControllerPath == null)
currentClass.defs = currentClass.defs.append(localizationNodesFactory.getLocaleFieldDecl());
currentClass.defs = currentClass.defs.append(localizationNodesFactory.getStringMethodDeclaration());
}
}
Expand Down
Loading

0 comments on commit 2b1178f

Please sign in to comment.