package com.psddev.cms.db;
import com.google.common.base.Preconditions;
import com.ibm.icu.text.DateFormat;
import com.ibm.icu.util.TimeZone;
import com.psddev.cms.tool.AuthenticationFilter;
import com.psddev.dari.util.PageContextFilter;
import com.psddev.dari.util.StringUtils;
import javax.servlet.http.HttpServletRequest;
import java.time.Instant;
import java.time.ZoneOffset;
import java.util.Date;
import java.util.Locale;
public final class Localization {
public static final String NOT_AVAILABLE_KEY = "label.notAvailable";
public static final String DATE_AND_TIME_SKELETON = "_dateAndTime";
public static final String DATE_ONLY_SKELETON = "_dateOnly";
public static final String TIME_ONLY_SKELETON = "_timeOnly";
private static final String SKELETON_DATE_KEY = "skeleton.date";
private static final String SKELETON_DATE_WITH_YEAR_KEY = "skeleton.dateWithYear";
private static final String SKELETON_TIME_KEY = "skeleton.time";
/**
* Localizes the given {@code key} using the given {@code locale} and
* {@code context}.
*
* <p>If there's isn't text associated with the given {@code key},
* returns the given {@code defaultText}.</p>
*
* @param locale Nullable. Default is {@link Locale#getDefault()}.
* @param context Nullable.
* @param key Nonnull.
* @param defaultText Nullable.
* @return Nonnull.
*/
public static String text(Locale locale, Object context, String key, String defaultText) {
LocalizationContext localizationContext = context instanceof LocalizationContext
? (LocalizationContext) context
: new LocalizationContext(context, null);
Locale defaultLocale = Locale.getDefault();
if (locale == null) {
locale = defaultLocale;
}
boolean firstTry = true;
while (true) {
String localized = localizationContext.text(locale, locale, key);
if (localized == null && !defaultLocale.equals(locale)) {
localized = localizationContext.text(defaultLocale, locale, key);
}
if (localized == null) {
Locale usLocale = Locale.US;
String usLanguage = usLocale.getLanguage();
if (!usLanguage.equals(defaultLocale.getLanguage())
&& !usLanguage.equals(locale.getLanguage())) {
localized = localizationContext.text(usLocale, locale, key);
}
}
if (localized != null) {
return localized;
}
if (firstTry) {
firstTry = false;
ObjectTypeResourceBundle.invalidateInstances();
} else if (StringUtils.isBlank(defaultText)) {
return localizationContext.missingText(key);
} else {
return defaultText;
}
}
}
/**
* Localizes the given {@code key} using the given {@code locale} and
* {@code context}.
*
* @param locale Nullable. Default is {@link Locale#getDefault()}.
* @param context Nullable.
* @param key Nonnull.
* @return Nonnull.
*/
public static String text(Locale locale, Object context, String key) {
return text(locale, context, key, null);
}
/**
* Localizes the given {@code timeMillis} in the given {@code timeZoneId}
* using the given {@code locale} and {@code skeleton}.
*
* @param locale Nullable. Default is {@link Locale#getDefault()}.
* @param timeZoneId Nullable. Default is {@link java.util.TimeZone#getDefault()}.
* @param timeMillis If negative, returns the localized text associated
* with {@link #NOT_AVAILABLE_KEY}.
* @param skeleton Nonnull.
* @return Nonnull.
*/
public static String date(Locale locale, String timeZoneId, long timeMillis, String skeleton) {
Preconditions.checkNotNull(skeleton);
if (locale == null) {
locale = Locale.getDefault();
}
if (timeMillis < 0) {
return text(locale, null, NOT_AVAILABLE_KEY);
}
if (timeZoneId == null) {
timeZoneId = java.util.TimeZone.getDefault().getID();
}
Date time = new Date(timeMillis);
if (DATE_AND_TIME_SKELETON.equals(skeleton)) {
skeleton = dateSkeleton(locale, timeMillis) + " " + text(locale, null, SKELETON_TIME_KEY);
} else if (DATE_ONLY_SKELETON.equals(skeleton)) {
skeleton = dateSkeleton(locale, timeMillis);
} else if (TIME_ONLY_SKELETON.equals(skeleton)) {
skeleton = text(locale, null, SKELETON_TIME_KEY);
}
DateFormat format = DateFormat.getInstanceForSkeleton(skeleton, locale);
format.setTimeZone(TimeZone.getTimeZone(timeZoneId));
return format.format(new Date(timeMillis));
}
private static String dateSkeleton(Locale locale, long timeMillis) {
return Instant.now().atOffset(ZoneOffset.UTC).getYear() == Instant.ofEpochMilli(timeMillis).atOffset(ZoneOffset.UTC).getYear()
? text(locale, null, SKELETON_DATE_KEY)
: text(locale, null, SKELETON_DATE_WITH_YEAR_KEY);
}
/**
* Localizes the given {@code key} using the current user's preferred
* locale and the given {@code context}.
*
* <p>If there's isn't text associated with the given {@code key},
* returns the given {@code defaultText}.</p>
*
* @param context Nullable.
* @param key Nonnull.
* @param defaultText Nullable.
* @return Nonnull.
*/
public static String currentUserText(Object context, String key, String defaultText) {
ToolUser user = currentUser();
Locale locale = user != null ? user.getLocale() : null;
return text(locale, context, key, defaultText);
}
/**
* Localizes the given {@code key} using the current user's preferred
* locale and the given {@code context}.
*
* @param context Nullable.
* @param key Nonnull.
* @return Nonnull.
*/
public static String currentUserText(Object context, String key) {
return currentUserText(context, key, null);
}
private static ToolUser currentUser() {
HttpServletRequest request = PageContextFilter.Static.getRequestOrNull();
return request != null ? AuthenticationFilter.Static.getUser(request) : null;
}
/**
* Localizes the given {@code timeMillis} in the current user's time zone
* using the current user's preferred locale and the given {@code format}.
*
* @param timeMillis If negative, returns the localized text associated
* with {@link #NOT_AVAILABLE_KEY}.
* @param format Nonnull.
* @return Nonnull.
*/
public static String currentUserDate(long timeMillis, String format) {
Preconditions.checkNotNull(format);
ToolUser user = currentUser();
Locale locale;
String timeZoneId;
if (user != null) {
locale = user.getLocale();
timeZoneId = user.getTimeZone();
} else {
locale = null;
timeZoneId = null;
}
return date(locale, timeZoneId, timeMillis, format);
}
// Prevent instantiation.
private Localization() {
}
}