From 3eec8f37745d36bd14ab25ee5bc5666aec323e16 Mon Sep 17 00:00:00 2001 From: Gmugra Date: Wed, 13 Dec 2017 18:27:42 +0100 Subject: [PATCH] refactoring, examples, README.md --- README.md | 86 +++++++++++++++++-- .../net/cactusthorn/localization/L10n.java | 22 +++-- .../localization/LocalizationLoader.java | 6 +- .../WatchLoggingLocalization.java | 2 +- .../java/net/cactusthorn/L10nExample.java | 29 +++++++ .../java/net/cactusthorn/SimpleExample.java | 31 +++++++ .../cactusthorn/localization/L10nTest.java | 9 +- .../localization/LocalizationLoaderTest.java | 6 +- .../localization/LoggingLocalizationTest.java | 2 +- .../WatchLoggingLocalizationTest.java | 4 +- .../localization/WithDefaultsTest.java | 2 +- 11 files changed, 169 insertions(+), 30 deletions(-) create mode 100644 src/test/java/net/cactusthorn/L10nExample.java create mode 100644 src/test/java/net/cactusthorn/SimpleExample.java diff --git a/README.md b/README.md index 82bf0a1..47af124 100644 --- a/README.md +++ b/README.md @@ -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. @@ -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 ``` diff --git a/src/main/java/net/cactusthorn/localization/L10n.java b/src/main/java/net/cactusthorn/localization/L10n.java index 2ad853d..f3a345d 100644 --- a/src/main/java/net/cactusthorn/localization/L10n.java +++ b/src/main/java/net/cactusthorn/localization/L10n.java @@ -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; @@ -39,9 +41,9 @@ public class L10n implements Localization { private L10n(String systemId, Path l10nDirectory, Charset charset, Class localizationClass) throws IOException { localization = new LocalizationLoader(systemId) - .setL10nDirectory(l10nDirectory) - .setCharset(charset) - .setClass(localizationClass) + .fromDirectory(l10nDirectory) + .encoded(charset) + .instanceOf(localizationClass) .load(); } @@ -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 localizationClass ) throws IOException { + public static L10n theOnlyAttemptToInitInstance(String systemId, Path l10nDirectory, Class localizationClass) { + return L10n.theOnlyAttemptToInitInstance(systemId, l10nDirectory, localizationClass, UTF_8); + } + + public static L10n theOnlyAttemptToInitInstance(String systemId, Path l10nDirectory, + Class localizationClass, Charset charset ) { L10n.systemId = systemId; L10n.l10nDirectory = l10nDirectory; @@ -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; diff --git a/src/main/java/net/cactusthorn/localization/LocalizationLoader.java b/src/main/java/net/cactusthorn/localization/LocalizationLoader.java index b196d71..52acf83 100644 --- a/src/main/java/net/cactusthorn/localization/LocalizationLoader.java +++ b/src/main/java/net/cactusthorn/localization/LocalizationLoader.java @@ -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 localizationClass) { + public LocalizationLoader instanceOf(Class localizationClass) { this.localizationClass = localizationClass; return this; } diff --git a/src/main/java/net/cactusthorn/localization/WatchLoggingLocalization.java b/src/main/java/net/cactusthorn/localization/WatchLoggingLocalization.java index 8f05519..a61f34a 100644 --- a/src/main/java/net/cactusthorn/localization/WatchLoggingLocalization.java +++ b/src/main/java/net/cactusthorn/localization/WatchLoggingLocalization.java @@ -49,7 +49,7 @@ public WatchLoggingLocalization(Map 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() { @Override diff --git a/src/test/java/net/cactusthorn/L10nExample.java b/src/test/java/net/cactusthorn/L10nExample.java new file mode 100644 index 0000000..6ec47a9 --- /dev/null +++ b/src/test/java/net/cactusthorn/L10nExample.java @@ -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")); + } + +} diff --git a/src/test/java/net/cactusthorn/SimpleExample.java b/src/test/java/net/cactusthorn/SimpleExample.java new file mode 100644 index 0000000..acfc938 --- /dev/null +++ b/src/test/java/net/cactusthorn/SimpleExample.java @@ -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(); + } + } +} diff --git a/src/test/java/net/cactusthorn/localization/L10nTest.java b/src/test/java/net/cactusthorn/localization/L10nTest.java index 8724446..8331b4c 100644 --- a/src/test/java/net/cactusthorn/localization/L10nTest.java +++ b/src/test/java/net/cactusthorn/localization/L10nTest.java @@ -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; @@ -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 diff --git a/src/test/java/net/cactusthorn/localization/LocalizationLoaderTest.java b/src/test/java/net/cactusthorn/localization/LocalizationLoaderTest.java index 4f1741e..b8b84c9 100644 --- a/src/test/java/net/cactusthorn/localization/LocalizationLoaderTest.java +++ b/src/test/java/net/cactusthorn/localization/LocalizationLoaderTest.java @@ -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 @@ -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 @@ -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(); } } diff --git a/src/test/java/net/cactusthorn/localization/LoggingLocalizationTest.java b/src/test/java/net/cactusthorn/localization/LoggingLocalizationTest.java index 59c7ae4..cc89630 100644 --- a/src/test/java/net/cactusthorn/localization/LoggingLocalizationTest.java +++ b/src/test/java/net/cactusthorn/localization/LoggingLocalizationTest.java @@ -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 diff --git a/src/test/java/net/cactusthorn/localization/WatchLoggingLocalizationTest.java b/src/test/java/net/cactusthorn/localization/WatchLoggingLocalizationTest.java index 6cf02ff..6cf4590 100644 --- a/src/test/java/net/cactusthorn/localization/WatchLoggingLocalizationTest.java +++ b/src/test/java/net/cactusthorn/localization/WatchLoggingLocalizationTest.java @@ -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") ); @@ -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"); diff --git a/src/test/java/net/cactusthorn/localization/WithDefaultsTest.java b/src/test/java/net/cactusthorn/localization/WithDefaultsTest.java index 335a8da..a775f33 100644 --- a/src/test/java/net/cactusthorn/localization/WithDefaultsTest.java +++ b/src/test/java/net/cactusthorn/localization/WithDefaultsTest.java @@ -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