Skip to content

Commit

Permalink
refactoring, examples, README.md
Browse files Browse the repository at this point in the history
  • Loading branch information
Gmugra committed Dec 13, 2017
1 parent 9dc8488 commit 3eec8f3
Show file tree
Hide file tree
Showing 11 changed files with 169 additions and 30 deletions.
86 changes: 81 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,15 @@ Not always useful ASIS because of several issues:
* formats which you can use to format parameters are not flexible enough.
* You need "format symbols" there. At least.

## Files
* The library required path to directory with UTF-8 encoded .properties files (since Java 6 java.util.Properties support that).
# Files
* The library required path to directory with .properties files (UTF-8 encoded, since Java 6 java.util.Properties support that).
* One "main" file per "locale". File names follow naming convention: languageTag.properties (e.g. **en-US.properties**, **ru-RU.properties** )
* FYI: https://docs.oracle.com/javase/8/docs/api/java/util/Locale.html#forLanguageTag-java.lang.String-

Note I: .properties file are not trendy but *imho* convenient: lightweight, native for java, support comments & line-breaking.
Note II: to support any other file-format need to implement specific *LocalizationLoader* class. Not a big deal actually.

### Default Files
## Default Files
Each localе can have optional "default" file. Content of "default" file(if it exists) load first, and than "overrided" by content from "main" file.
Overriding is working key by key, including system settings and formats.
It gives possible to override, for example, only one plural version of language key, one system setting or one property of specific format.
Expand Down Expand Up @@ -142,12 +142,88 @@ key.with.parameters = Super {{param1}} value {{cooldate,iso8601}} xxxx {{prc,per
```

* Positions of parameters in the text play no any role.
* Parameters of the same key, in different language files can have different positions in th text.
* Parameters of the same key, in different language files can have different positions in the text.
* Parameters of the same key, in different language files can have different formats
* Same key, in different language files can have different set of parameters
* All of that true also for different count number/pluralization form index in the same language file

## License
# How to use

## Basic example:
```
package net.cactusthorn;
import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Locale;
import net.cactusthorn.localization.Localization;
import net.cactusthorn.localization.LocalizationLoader;
public class SimpleExample {
static Locale en_US = Locale.forLanguageTag("en-US");
public static void main(String... args) {
try {
String systemId = args[0];
Path l10nDirectory = Paths.get(args[1]);
Localization localization = new LocalizationLoader(systemId ).fromDirectory(l10nDirectory ).load();
System.out.println(localization.get(en_US, "super.key"));
} catch (IOException e ) {
e.printStackTrace();
}
}
}
```

By default *LocalizationLoader* create an instance of *net.cactusthorn.localization.BasicLocalization* and load it with files from "L10n" directory, using UTF-8 character set. Only **systemId** is required.

"Full" LocalizationLoader call:
```
new LocalizationLoader("my-system-id").instanceOf(BasicLocalization.class).fromDirectory(l10nDirectory ).encoded(StandardCharsets.UTF_8).load();
```

## Implementations
Exist several implementations which are using same files, but provide different behaviors:
* BasicLocalization - straightforward implementation, which simple throw some exception in any "wrong" situation
* LoggingLocalization - never(except initialization phase) throw exceptions, but right them in the log(Slf4j) instead. Methods calls always return some string, with as much as possible data.
* https://www.slf4j.org/
* WatchLoggingLocalization - LoggingLocalization which run Thread with WatchService to, on the fly, upload changes from the files
* https://docs.oracle.com/javase/8/docs/api/java/nio/file/WatchService.html

## Parameters
*Localization* interface contain several version of *get* method to work with parameters in deferent forms.
Most simple way is *net.cactusthorn.localization.Parameter* class:
Example:
```
l10n.get(en_US, "some-key", Parameter.count(33), Parameter.of("param-name-1", "value"), Parameter.of("param-name-2", some-object));
```

## L10n Singleton
Initialization-on-demand holder of any implementation.
* https://en.wikipedia.org/wiki/Initialization-on-demand_holder_idiom

Keep in mind, that it's not clean according to true OOD to have and use something like that.
It's working just fine, but: if you need something like that, you have problems with design of your application.

You should be careful. Problems with it:
* can throw exception during initialization(when something wrong with the files), and this fact breaks initialization-on-demand pattern, actually
* if it fail at initialization moment, it will be dead till restart of the application. NoClassDefFoundError.
* need 4 parameters for initialization

Trade-off: need to originize first single call of the class with special initialization method somewhere at very start of your application.
Example: *net.cactusthorn.L10nExample*

# License

Released under the BSD 2-Clause License
```
Expand Down
22 changes: 14 additions & 8 deletions src/main/java/net/cactusthorn/localization/L10n.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
******************************************************************************/
package net.cactusthorn.localization;

import static java.nio.charset.StandardCharsets.UTF_8;

import java.io.IOException;
import java.nio.charset.Charset;
import java.nio.file.Path;
Expand Down Expand Up @@ -39,9 +41,9 @@ public class L10n implements Localization {
private L10n(String systemId, Path l10nDirectory, Charset charset, Class<? extends AbstractLocalization> localizationClass) throws IOException {
localization =
new LocalizationLoader(systemId)
.setL10nDirectory(l10nDirectory)
.setCharset(charset)
.setClass(localizationClass)
.fromDirectory(l10nDirectory)
.encoded(charset)
.instanceOf(localizationClass)
.load();
}

Expand Down Expand Up @@ -71,11 +73,15 @@ private static L10n initLocalizationHolder() {
}

/**
* This method must be called before anything else from this class.
* The method theOnlyAttemptToInitInstance must be called before anything else from this class.
* Compromise for practical usage...
* */
public static L10n theOnlyAttemptToInitInstance(String systemId, Path l10nDirectory, Charset charset,
Class<? extends AbstractLocalization> localizationClass ) throws IOException {
public static L10n theOnlyAttemptToInitInstance(String systemId, Path l10nDirectory, Class<? extends AbstractLocalization> localizationClass) {
return L10n.theOnlyAttemptToInitInstance(systemId, l10nDirectory, localizationClass, UTF_8);
}

public static L10n theOnlyAttemptToInitInstance(String systemId, Path l10nDirectory,
Class<? extends AbstractLocalization> localizationClass, Charset charset ) {

L10n.systemId = systemId;
L10n.l10nDirectory = l10nDirectory;
Expand All @@ -87,8 +93,8 @@ public static L10n theOnlyAttemptToInitInstance(String systemId, Path l10nDirect
try {
instance = InstanceHolder.INSTANCE;
} catch (ExceptionInInitializerError e) {
Throwable exceptionInInit = e.getCause();
throw new IOException(exceptionInInit.getMessage(), exceptionInInit.getCause());
Throwable exceptionInInit = e.getCause();
throw new RuntimeException(new IOException(exceptionInInit.getMessage(), exceptionInInit.getCause()));
}

return instance;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,17 +45,17 @@ public LocalizationLoader(String systemId) {
this.systemId = systemId;
}

public LocalizationLoader setL10nDirectory(Path l10nDirectory) {
public LocalizationLoader fromDirectory(Path l10nDirectory) {
this.l10nDirectory = l10nDirectory;
return this;
}

public LocalizationLoader setCharset(Charset charset) {
public LocalizationLoader encoded(Charset charset) {
this.charset = charset;
return this;
}

public LocalizationLoader setClass(Class<? extends AbstractLocalization> localizationClass) {
public LocalizationLoader instanceOf(Class<? extends AbstractLocalization> localizationClass) {
this.localizationClass = localizationClass;
return this;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ public WatchLoggingLocalization(Map<Locale, LocalizationKeys> translations, Stri

l10nDirectory.register(watchService, ENTRY_CREATE, ENTRY_MODIFY);

loader = new LocalizationLoader(this.systemId).setL10nDirectory(this.l10nDirectory).setCharset(charset);
loader = new LocalizationLoader(this.systemId).fromDirectory(this.l10nDirectory).encoded(charset);

Files.walkFileTree(l10nDirectory, new SimpleFileVisitor<Path>() {
@Override
Expand Down
29 changes: 29 additions & 0 deletions src/test/java/net/cactusthorn/L10nExample.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package net.cactusthorn;

import java.net.URISyntaxException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Locale;

import net.cactusthorn.localization.BasicLocalizationTest;
import net.cactusthorn.localization.L10n;
import net.cactusthorn.localization.LoggingLocalization;

public class L10nExample {

static Locale en_US = Locale.forLanguageTag("en-US");

static {
try {
Path path = Paths.get(BasicLocalizationTest.class.getClassLoader().getResource("L10n").toURI());
L10n.theOnlyAttemptToInitInstance("test-app", path, LoggingLocalization.class);
} catch (URISyntaxException e) {
e.printStackTrace();
}
}

public static void main(String... args) {
System.out.println(L10n.instance().get(en_US, "super.key"));
}

}
31 changes: 31 additions & 0 deletions src/test/java/net/cactusthorn/SimpleExample.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package net.cactusthorn;

import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Locale;

import net.cactusthorn.localization.Localization;
import net.cactusthorn.localization.LocalizationLoader;

public class SimpleExample {

static Locale en_US = Locale.forLanguageTag("en-US");

public static void main(String... args) {

try {

String systemId = args[0];

Path l10nDirectory = Paths.get(args[1]);

Localization localization = new LocalizationLoader(systemId ).fromDirectory(l10nDirectory ).load();

System.out.println(localization.get(en_US, "super.key"));

} catch (IOException e ) {
e.printStackTrace();
}
}
}
9 changes: 3 additions & 6 deletions src/test/java/net/cactusthorn/localization/L10nTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,11 @@

import static org.junit.Assert.assertEquals;

import java.io.IOException;
import java.net.URISyntaxException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Locale;

import static java.nio.charset.StandardCharsets.UTF_8;

import org.junit.BeforeClass;
import org.junit.Rule;
import org.junit.Test;
Expand All @@ -33,16 +30,16 @@ public class L10nTest {
public final SystemOutRule systemOutRule = new SystemOutRule().muteForSuccessfulTests();

@BeforeClass
public static void before() throws URISyntaxException, IOException, InterruptedException {
public static void before() throws URISyntaxException, InterruptedException {

Path l10nDirectory = Paths.get(L10nTest.class.getClassLoader().getResource("L10n").toURI());

//The file-watching-thread from WatchLoggingLocalization will work just fine.
//Note: the test can finish too fast and JUnit kill that all, so you will see nothing in log
//However, to see that is possible and working - uncomment next line
//L10n.theOnlyAttemptToInitInstance("test-app", l10nDirectory, UTF_8, WatchLoggingLocalization.class);
//L10n.theOnlyAttemptToInitInstance("test-app", l10nDirectory, WatchLoggingLocalization.class);

L10n.theOnlyAttemptToInitInstance("test-app", l10nDirectory, UTF_8, LoggingLocalization.class);
L10n.theOnlyAttemptToInitInstance("test-app", l10nDirectory, LoggingLocalization.class);
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ public void testNotDirectory() throws URISyntaxException, IOException {
expectedException.expectMessage(containsString("is not directory"));

Path path = Paths.get(BasicLocalizationTest.class.getClassLoader().getResource("L10n/ru-RU.properties").toURI());
new LocalizationLoader("test-app").setL10nDirectory(path).load();
new LocalizationLoader("test-app").fromDirectory(path).load();
}

@Test
Expand All @@ -53,7 +53,7 @@ public void testWrongSystemId() throws URISyntaxException, IOException {
);

Path path = Paths.get(BasicLocalizationTest.class.getClassLoader().getResource("WrongSystemId").toURI());
new LocalizationLoader("my-super-app").setL10nDirectory(path).setClass(LoggingLocalization.class).load();
new LocalizationLoader("my-super-app").fromDirectory(path).instanceOf(LoggingLocalization.class).load();
}

@Test
Expand All @@ -69,6 +69,6 @@ public void testWrongLanguageTag() throws URISyntaxException, IOException {
);

Path path = Paths.get(BasicLocalizationTest.class.getClassLoader().getResource("WrongLanguageTag").toURI());
new LocalizationLoader("test-app").setL10nDirectory(path).load();
new LocalizationLoader("test-app").fromDirectory(path).load();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ public class LoggingLocalizationTest {
@BeforeClass
public static void loadL10n() throws IOException {

localization = new LocalizationLoader("test-app").setClass(LoggingLocalization.class).load();
localization = new LocalizationLoader("test-app").instanceOf(LoggingLocalization.class).load();
}

@Rule
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ public void testCopyNew() throws URISyntaxException, IOException, InterruptedExc

Path l10nDirectory = prepare();

Localization localization = new LocalizationLoader("test-app").setL10nDirectory(l10nDirectory).setClass(WatchLoggingLocalization.class).load();
Localization localization = new LocalizationLoader("test-app").fromDirectory(l10nDirectory).instanceOf(WatchLoggingLocalization.class).load();

assertEquals("Locale: ru-RU, Unavailable locale", localization.get(ru_RU, "super.key") );

Expand All @@ -61,7 +61,7 @@ public void testFail() throws URISyntaxException, IOException, InterruptedExcept

Path l10nDirectory = prepare();

Localization localization = new LocalizationLoader("test-app").setL10nDirectory(l10nDirectory).setClass(WatchLoggingLocalization.class).load();
Localization localization = new LocalizationLoader("test-app").fromDirectory(l10nDirectory).instanceOf(WatchLoggingLocalization.class).load();

copy(l10nDirectory,"WrongLanguageTag", "fr-CA.properties");

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ public static void load() throws URISyntaxException, IOException {

Path l10nDirectory = Paths.get(WithDefaultsTest.class.getClassLoader().getResource("L10nWithDefault").toURI());

localization = new LocalizationLoader("test-app").setL10nDirectory(l10nDirectory).load();
localization = new LocalizationLoader("test-app").fromDirectory(l10nDirectory).load();
}

@Test
Expand Down

0 comments on commit 3eec8f3

Please sign in to comment.