Separate language and format locales in a Wicket app? - java

Our Wicket app needs separate UI language and number/date format locales (e.g. UI in english, Number and date format: German) per user.
If you set the session locale to say Locale.GERMAN, you get both german number and date format AND german resources (e.g. MyForm_de.properties).
We worked around this by setting the session locale to the number and date locale and then use a custom ComponentStringResourceLoader to load strings (return super.loadStringResource(clazz, key, language != null ? new Locale(language) : locale, style, variation)). However, it looks like strings are being cached because if I log on as different users, I start getting a mixture of languages.
Anyone know to control the caching (assuming that is causing the problem)? Note: I don't want to prevent caching (since that would presumably hurt performance). I guess I want to override the caching behavior so it works correctly with our custom resource loader.
Or is there a better approach altogether to solving this problem?
Here's the code we used for the custom StringResourceLoader.
ComponentStringResourceLoader myComponentStringResourceLoader = new ComponentStringResourceLoader() {
#Override
public String loadStringResource(Class<?> clazz, String key, Locale locale, String style, String variation) {
return super.loadStringResource(clazz, key, getLoggedOnUser().getUILanguageLocale(), style, variation);
}
};
getResourceSettings().getStringResourceLoaders().add(0, myComponentStringResourceLoader);
Here's the code to set the session locale (used for number and date formatting).
getSession().setLocale(getLoggedOnUser().getNumberAndDateLocale());

You can use Session's locale for i18n of the labels and either override #getLocale() or #getConverter() for the components which should use the different locale for dates. I guess you talk about TextField which needs to render its value in German locale. If so, just create GermanTextField that always returns Locale.GERMAN in its #getLocale().

Related

How to use String.format(Locale, ...)?

I am new in Android development and I'm trying this code:
String hms = String.format("%02d:%02d:%02d",
TimeUnit.MILLISECONDS.toHours(milliSeconds),
TimeUnit.MILLISECONDS.toMinutes(milliSeconds) -
TimeUnit.HOURS.toMinutes(TimeUnit.MILLISECONDS.toHours(milliSeconds)),
TimeUnit.MILLISECONDS.toSeconds(milliSeconds) -
TimeUnit.MINUTES.toSeconds(TimeUnit.MILLISECONDS.toMinutes(milliSeconds)));
I get the message :
Implicitly using the default locale is a common source of bugs:
Use String.format(Locale, ...) instead
I have no idea how to modify the code in order to implement the recommendation.
Most likely you don't need to do anything. That's a warning telling you that you didn't specify a locale for String.format, so it's using the default locale. That can cause a bug in some circumstances, but it's unlikely to in yours. If you want to be careful you can pass in an explicit locale, or you can just ignore the warning. Formatting numbers like this without any type of currency is fairly safe.
(The bugs you'll see are if the locale your device is in has specific formatting rules for things. The big one I know of that's hit me is that Turkish has a letter i who's capital symbol is different than the english letter I.)

Customize a Locale in Java

In Java the Locale defines things that are related how people want to see things (like currency formats, the name of the months and when a week starts).
When parsing the name of a Month (with a DateTimeFormatter) it starts to become tricky.
If you use Locale.US or Locale.ENGLISH then September has the short form Sep.
If you use Locale.UK then September also has the short form Sep in Java 11 ... but when you try Java 17 then it has Sept (because of changes at the Unicode CLDR end for which I asked if this was correct).
The effect is that my tests started failing when trying to build with Java 17.
The reason my current code uses Locale.UK instead of Locale.ENGLISH is because in Java Locale.ENGLISH is actually not just English but also the non-ISO American way of defining a week (they use Sunday as the first day of the week). I want to have it the ISO way.
Simply:
WeekFields.ISO = WeekFields.of(Locale.UK) = WeekFields[MONDAY,4]
WeekFields.of(Locale.ENGLISH) = WeekFields.of(Locale.US) = WeekFields[SUNDAY,1]
So starting with Java 17 I have not yet been able to find a built in Locale that works correctly.
In my mind I have to take either the Locale.ENGLISH and change the WeekFields or take the Locale.UK and change the shortname of the month September to what I need.
My question is how do I do this (in Java 17)?
Or is there a better way to fix this?
Update 1:
I already got feedback from the people at Unicode indicating that the change for en_GB to use Sept instead of Sep is a bugfix because that is the way it should be abbreviated in the UK.
So it seems I will need not just a parser that accepts "Sep" but one that will accept a mix of "Sept" and "Sep" for English.
Update 2:
I have tweaked my code that in case of a parse exception it will try to change what is assumed to be the input ("Sep") into what the currently selected locate likes to have. This does not cover all cases, it covers enough cases for my specific situation.
For those interested: my commit.
I found a way of handling this by using SPI.
I'm documenting it here as a possibility that may work for others (it does not work for my context).
As an experiment I created a class:
package nl.basjes.parse.httpdlog.dissectors.locale;
import java.util.Locale;
import java.util.spi.CalendarDataProvider;
import static java.util.Calendar.MONDAY;
public class CalendarDataProviderISO8601 extends CalendarDataProvider {
public static final Locale ENGLISH_ISO = new Locale("en", "", "ISO");
#Override
public int getFirstDayOfWeek(Locale locale) {
return MONDAY;
}
#Override
public int getMinimalDaysInFirstWeek(Locale locale) {
return 4;
}
#Override
public Locale[] getAvailableLocales() {
return new Locale[]{ENGLISH_ISO};
}
}
and a file ./src/main/resources/META-INF/services/java.util.spi.CalendarDataProvider with
nl.basjes.parse.httpdlog.dissectors.locale.CalendarDataProviderISO8601
Because this is just a variant over the regionless "English" it will take everything from "English" and put the above class over it.
Although this works I cannot use it.
The problem is that although http://openjdk.java.net/jeps/252 describes The default lookup order will be CLDR, COMPAT, SPI, the current reality is that the SPI has been removed from this list in this change because of deprecating the Extension Mechanism.
So to use this construct the class must be in the classpath at startup and the commandline option -Djava.locale.providers=CLDR,COMPAT,SPI must be passed to the JVM.
Given that my library ( https://github.com/nielsbasjes/logparser/ ) is also used in situations (like Apache Flink/Beam/Drill/Pig) where classes are shipped in a more dynamic way (serialized and transported to an already running JVM) to multiple machines this construct cannot be used.
I currently do not know of a dynamic way of doing something like this in Java.

how to use #StringDef in an app with i18n

#StringDef is great for replacing Enum.
#StringDef(Standard, Mini)
public #interface Type;
But what if my app needs to support english, french and chinese?
So the standard might be "Standard", "Norme", and "标准" in three different countries. In that case, the Standard and Mini may need the context to get the real value.
How could we still use #StringDef in an app with i18n.
There is no out-of-the-box solution.
The recommended way is to store localized string resources in different directories (res/values-XX/strings.xml, where XX is en, fr etc).
https://developer.android.com/guide/topics/resources/localization.html
In this approach you need to use string id to get the localized string. You can try the following:
Use ids of strings instead:
#IntDef({R.string.standard, R.string.mini})
Use some helper to match #StringDef's string to localized string resorce.
Use enums.

Saving Locale in a Database

On my JSF 2.1 Application with JDK 7, I searched for a solution to provide my users the possibility to save their preferred language. So, on the next login the language should received from the Database and replaced by the language from the default (Browser).
My only problem is now, how to save the java.util.Locale in my database?
After hours of googling I found a new functionality inside of JDK 7 that is the "forLanguageTag Factory Method". This method returns a Locale and only needs for that a IETF BCP 47 standard string.
This sounds really simple and great for me. But how can I get this "IETF BCP 47 standard"-String from an existing locale? I looked in the API but found nothing that is comparable to the "IETF BCP 47 standard".
Consider Locale.toLanguageTag:
Returns a well-formed IETF BCP 47 language tag representing [a]
locale.
Example usage:
String expectedTag = "en-US";
Locale locale = Locale.forLanguageTag( expectedTag );
String actualTag = locale.toLanguageTag();
Assert.assertEquals( expectedTag, actualTag );
Note: There are some restrictions as mentioned in the javadocs.
If you're just interested in the language, simply use Locale.getLanguage() to transform a locale into a String, and new Locale(String language) to transform the String into a Locale.
If you want to store the whole Locale, use Locale.toString(), and a custom method that splits on _ to transform the string into the three parts of a Locale.

How to customize currency formatting in MessageFormat in ICU4J

I have a system that generates a lot of documents. Its contents are defined in ResourceBundles.
I want to customize the way MessageFormat prints currency values. Sometimes I want it to display currencies without fraction digits (but not always).
This should be working as expected but it is not:
System.err.println(
com.ibm.icu.text.MessageFormat.format(
"{0,number,\u00A4#}",
new com.ibm.icu.util.CurrencyAmount(1,
com.ibm.icu.util.Currency.getInstance("USD"))));
Unfortunately it prints out:
US$1,00
Does anyone of you use custom formats for currency in resource bundle 'properties' files?
I don't want to change it system wide.
And by the way this works fine with java.text.MessageFormat.
OK, I read your question once again.
I don't really know why you want to chop down the cents part (in US, it makes sense in Korea or Japan as they don't use them at all).
Anyway, I don't think it is a good idea to just cut-off cents part, but if you want to do it, it is as simple as using NumberFormat with setMaximumIntegerDigits(int).
BTW, I still don't know I know why by using resource bundles you can't use NumberFormat.
You still can call formatter in MessageFormat.format():
NumberFormat currencyFormatter = NumberFormat.getCurrencyInstance(Locale.US);
currencyFormatter.setMaximumFractionDigits(0);
System.err.println(MessageFormat.format("Some amount: {0}.",
currencyFormatter.format(1d)));
Predictably it prints out:
Some amount: $1.
If you need to retain the currency, I'd suggest to play with setCurrency(Currency) method by retain local format - you are asking this question in Internalization tag anyway.
Edit: Including information about MessageFormat capabilities
If you need to use custom currency formats for a Locale, you actually need to instantiate MessageFormat class (regular static MessageFormat.format(String, Object...) won't work in web applications for it uses default Locale - Locale.getDefault(Locale.Category.FORMAT) in Java 7 - server Locale if you prefer).
So what you really want is to write a helper method (sorry, no bonus) that will look similar to this (out of memory, sorry):
public static String format(String pattern, Locale locale, Object... args) {
final String emptyPattern = "";
final FieldPosition zero = new FieldPosition(0);
MessageFormat fmt = new MessageFormat(emptyPattern, locale);
StringBuffer buf = new StringBuffer(); // I just love it...
fmt.applyPattern(pattern);
fmt.format(args, buf, zero);
return buf.toString();
}
For performance reasons, you might think of creating StringBuffer once and then clean it all the time, but I leave optimizations to yourself.
You also would need to modify patterns a bit and I will explain in a moment why:
String pattern = "{1}{0,number,\u00A4#}";
You would need to pass the amount and the currency symbol and left to translators where to place the symbol and how to format value for a Locale (don't forget to add comments to properties file!).

Categories