/*
*
* Copyright (C) 2007-2015 Licensed to the Comunes Association (CA) under
* one or more contributor license agreements (see COPYRIGHT for details).
* The CA licenses this file to you under the GNU Affero General Public
* License version 3, (the "License"); you may not use this file except in
* compliance with the License. This file is part of kune.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
package cc.kune.core.client.i18n;
import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.MissingResourceException;
import java.util.Set;
import cc.kune.common.client.log.Log;
import cc.kune.common.client.notify.NotifyUser;
import cc.kune.common.client.utils.MetaUtils;
import cc.kune.common.client.utils.WindowUtils;
import cc.kune.common.shared.i18n.I18nTranslationService;
import cc.kune.common.shared.utils.Pair;
import cc.kune.common.shared.utils.SimpleResponseCallback;
import cc.kune.common.shared.utils.TextUtils;
import cc.kune.core.client.events.UserSignInEvent;
import cc.kune.core.client.events.UserSignInEvent.UserSignInHandler;
import cc.kune.core.client.rpcservices.I18nServiceAsync;
import cc.kune.core.client.state.Session;
import cc.kune.core.client.state.SiteParameters;
import cc.kune.core.shared.dto.I18nLanguageDTO;
import com.google.gwt.core.client.Scheduler;
import com.google.gwt.core.client.Scheduler.RepeatingCommand;
import com.google.gwt.i18n.client.DateTimeFormat;
import com.google.gwt.i18n.client.LocaleInfo;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.inject.Inject;
import com.google.inject.Singleton;
import com.google.web.bindery.event.shared.EventBus;
// TODO: Auto-generated Javadoc
/**
* The Class I18nUITranslationService.
*
* @author vjrj@ourproject.org (Vicente J. Ruiz Jurado)
*/
@Singleton
public class I18nUITranslationService extends I18nTranslationService {
/**
* The Interface I18nLanguageChangeNeeded.
*
* @author vjrj@ourproject.org (Vicente J. Ruiz Jurado)
*/
public interface I18nLanguageChangeNeeded {
/**
* On change needed.
*/
void onChangeNeeded();
/**
* On change not needed.
*/
void onChangeNotNeeded();
}
/** The current lang. */
private I18nLanguageDTO currentLang;
/** The current language code. */
private String currentLanguageCode;
/** The early texts. */
private final Set<Pair<String, String>> earlyTexts;
/** The i18n service. */
private final I18nServiceAsync i18nService;
/** The is current lang rtl. */
private boolean isCurrentLangRTL = false;
/** The is lang in properties. */
private boolean isLangInProperties;
/** The kune constants. */
private final KuneConstants kuneConstants;
/** The lexicon. */
private HashMap<String, String> lexicon;
/** The session. */
private final Session session;
/** The site common name. */
private String siteCommonName;
/**
* Instantiates a new i18n ui translation service.
*
* @param session
* the session
* @param i18nService
* the i18n service
* @param eventBus
* the event bus
* @param kuneConstants
* the kune constants
*/
@Inject
public I18nUITranslationService(final Session session, final I18nServiceAsync i18nService,
final EventBus eventBus, final KuneConstants kuneConstants) {
this.session = session;
this.i18nService = i18nService;
this.kuneConstants = kuneConstants;
final String locale = WindowUtils.getParameter(SiteParameters.LOCALE);
final LocaleInfo currentLocale = LocaleInfo.getCurrentLocale();
Log.info("Workspace starting with language: " + currentLocale.getLocaleName() + ", isRTL: "
+ LocaleInfo.getCurrentLocale().isRTL() + ", translated langs: "
+ Arrays.toString(LocaleInfo.getAvailableLocaleNames()));
isLangInProperties = isInConstantProperties(currentLocale.getLocaleName());
earlyTexts = new HashSet<Pair<String, String>>();
i18nService.getInitialLanguage(locale, new AsyncCallback<I18nLanguageDTO>() {
@Override
public void onFailure(final Throwable caught) {
Log.error("Workspace adaptation to your language failed: " + caught.getMessage());
}
@Override
public void onSuccess(final I18nLanguageDTO result) {
currentLang = result;
currentLanguageCode = currentLang.getCode();
session.setCurrentLanguage(currentLang);
isLangInProperties = isInConstantProperties(currentLang.getCode());
i18nService.getLexicon(currentLang.getCode(), new AsyncCallback<HashMap<String, String>>() {
@Override
public void onFailure(final Throwable caught) {
Log.error("Workspace adaptation to server proposed language failed: " + caught.getMessage());
}
@Override
public void onSuccess(final HashMap<String, String> result) {
lexicon = result;
session.setCurrentLanguage(currentLang);
Log.info("Workspace adaptation to server proposed language: " + currentLang.getEnglishName()
+ ", isRTL: " + currentLang.getDirection() + " use properties: "
+ shouldIuseProperties());
changeToLanguageIfNecessary(getCurrentGWTlanguage(), currentLang.getCode(),
currentLang.getEnglishName(), false, new I18nLanguageChangeNeeded() {
@Override
public void onChangeNeeded() {
}
@Override
public void onChangeNotNeeded() {
isCurrentLangRTL = currentLang.getDirection().equals(RTL);
eventBus.fireEvent(new I18nReadyEvent());
I18nStyles.setRTL(isCurrentLangRTL);
}
});
}
});
session.onUserSignIn(true, new UserSignInHandler() {
@Override
public void onUserSignIn(final UserSignInEvent event) {
Scheduler.get().scheduleIncremental(new RepeatingCommand() {
@Override
public boolean execute() {
if (!earlyTexts.isEmpty()) {
final Pair<String, String> pair = earlyTexts.iterator().next();
save(pair.getLeft(), pair.getRight());
earlyTexts.remove(pair);
}
return !earlyTexts.isEmpty();
}
});
}
});
}
});
}
/**
* Change to language if necessary.
*
* @param wantedLang
* the wanted lang
* @param wantedLangEnglishName
* the wanted lang english name
* @param listener
* the listener
*/
public void changeToLanguageIfNecessary(final String wantedLang, final String wantedLangEnglishName,
final boolean ask, final I18nLanguageChangeNeeded listener) {
changeToLanguageIfNecessary(currentLang.getCode(), wantedLang, wantedLangEnglishName, ask, listener);
}
/**
* Change to language if necessary.
*
* @param currentLangCode
* the current lang code
* @param wantedLang
* the wanted lang
* @param wantedLangEnglishName
* the wanted lang english name
* @param listener
* the listener
* @return true if we should reload the client with the new language
*/
private void changeToLanguageIfNecessary(final String currentLangCode, final String wantedLang,
final String wantedLangEnglishName, final boolean ask, final I18nLanguageChangeNeeded listener) {
if (!currentLangCode.equals(wantedLang) && isInConstantProperties(wantedLang)) {
if (!ask) {
listener.onChangeNeeded();
setCurrentLanguage(wantedLang);
I18nUrlUtils.changeLanguageInUrl(wantedLang);
} else {
NotifyUser.askConfirmation(t("Confirm please"),
t("Do you want to reload this page to use '[%s]' language?", wantedLangEnglishName),
new SimpleResponseCallback() {
@Override
public void onCancel() {
// User no accepted to change the language...
listener.onChangeNotNeeded();
}
@Override
public void onSuccess() {
// User accepted to change the language...
listener.onChangeNeeded();
setCurrentLanguage(wantedLang);
I18nUrlUtils.changeLanguageInUrl(wantedLang);
}
});
}
} else {
listener.onChangeNotNeeded();
}
}
/**
* Format date with locale.
*
* @param date
* the date
* @return the string
*/
public String formatDateWithLocale(final Date date) {
return formatDateWithLocale(date, false);
}
/**
* Format date with locale.
*
* @param date
* the date
* @param shortFormat
* the short format
* @return the string
*/
public String formatDateWithLocale(final Date date, final boolean shortFormat) {
String dateFormat = shortFormat ? currentLang.getDateFormatShort() : currentLang.getDateFormat();
final DateTimeFormat fmt;
if (dateFormat == null) {
fmt = DateTimeFormat.getFormat("M/d/yyyy h:mm a");
} else {
if (shortFormat) {
fmt = DateTimeFormat.getFormat(dateFormat + " h:mm a");
} else {
final String abrevMonthInEnglish = DateTimeFormat.getFormat("MMM").format(date);
final String monthToTranslate = abrevMonthInEnglish + " [%NT abbreviated month]";
dateFormat = dateFormat.replaceFirst("MMM", "'" + t(monthToTranslate) + "'");
fmt = DateTimeFormat.getFormat(dateFormat + " h:mm a");
}
}
final String dateFormated = fmt.format(date);
return dateFormated;
}
/**
* Gets the current gw tlanguage.
*
* @return the current gw tlanguage
*/
private String getCurrentGWTlanguage() {
String gwtLang = LocaleInfo.getCurrentLocale().getLocaleName();
gwtLang = gwtLang.equals("default") ? "en" : gwtLang;
return gwtLang;
}
/**
* Gets the current language.
*
* @return the current language
*/
public String getCurrentLanguage() {
return currentLanguageCode;
}
/**
* Gets the lexicon.
*
* @return the lexicon
*/
public HashMap<String, String> getLexicon() {
return lexicon;
}
/**
* Gets the site common name.
*
* @return the site common name
*/
@Override
public String getSiteCommonName() {
if (siteCommonName == null) {
final String meta = MetaUtils.get("kune.default.site.commonname");
siteCommonName = (meta == null ? t("this site") : t(meta));
}
return siteCommonName;
}
/**
* Gets the trans from bd.
*
* @param text
* the text
* @param noteForTranslators
* the note for translators
* @param encodeText
* the encode text
* @return the trans from bd
*/
private String getTransFromBD(final String text, final String noteForTranslators,
final String encodeText) {
if (lexicon == null) {
Log.info("i18n not initialized: " + text);
earlyTexts.add(Pair.create(text, noteForTranslators));
Log.info("i18n pending translations: " + earlyTexts.size());
return text;
}
String translation = lexicon.get(encodeText);
if (lexicon.containsKey(encodeText)) {
if (translation == UNTRANSLATED_VALUE) {
// Not translated but in db, return text
translation = encodeText;
}
} else {
// Not translated and not in db, make a petition for translation (if
// enabled)
if (session.isLogged() && session.getInitData().getStoreUntranslatedStrings()) {
save(text, noteForTranslators);
lexicon.put(encodeText, UNTRANSLATED_VALUE);
} else {
earlyTexts.add(Pair.create(text, noteForTranslators));
}
translation = encodeText;
}
return decodeHtml(translation);
}
/**
* Checks if is in constant properties.
*
* @param currentLang
* the current lang
* @return true, if is in constant properties
*/
private boolean isInConstantProperties(final String currentLang) {
for (final String lang : LocaleInfo.getAvailableLocaleNames()) {
if (lang.equals(currentLang)) {
Log.info("Workspace adaptation to language: " + currentLang + " is in KuneConstants*properties");
return true;
}
}
Log.info("Workspace adaptation to language: " + currentLang + " is not in KuneConstants*properties");
return false;
}
/**
* Checks if is ready.
*
* @return true, if is ready
*/
public boolean isReady() {
return lexicon != null;
}
/*
* (non-Javadoc)
*
* @see cc.kune.common.shared.i18n.I18nTranslationService#isRTL()
*/
@Override
public boolean isRTL() {
return isCurrentLangRTL;
}
/**
* Save.
*
* @param text
* the text
* @param noteForTranslators
* the note for translators
*/
private void save(final String text, final String noteForTranslators) {
i18nService.getTranslation(session.getUserHash(), currentLanguageCode, text, noteForTranslators,
new AsyncCallback<String>() {
@Override
public void onFailure(final Throwable caught) {
}
@Override
public void onSuccess(final String result) {
Log.debug("Registered in db '" + text + "' as pending translation");
}
});
}
/**
* Sets the current language.
*
* @param newLanguage
* the new current language
*/
public void setCurrentLanguage(final String newLanguage) {
this.currentLanguageCode = newLanguage;
}
/**
* Sets the lexicon.
*
* @param lexicon
* the lexicon
*/
public void setLexicon(final HashMap<String, String> lexicon) {
this.lexicon = lexicon;
}
/**
* Sets the translation after save.
*
* @param text
* the text
* @param translation
* the translation
*/
public void setTranslationAfterSave(final String text, final String translation) {
lexicon.put(text, translation);
}
/**
* Should iuse properties.
*
* @return true, if successful
*/
private boolean shouldIuseProperties() {
return isLangInProperties;
}
/**
* In production, this method uses a hashmap. In development, if the text is
* not in the hashmap, it makes a server petition (that stores the text
* pending for translation in db).
*
* Warning: text is escaped as html before insert in the db. Don't use html
* here (o user this method with params).
*
* @param text
* the text
* @param noteForTranslators
* the note for translators
* @return text translated in the current language
*/
@Override
public String tWithNT(final String text, final String noteForTranslators) {
if (TextUtils.empty(text)) {
return text;
}
final String encodeText = TextUtils.escapeHtmlLight(text);
if (shouldIuseProperties()) {
// The db translations now are in properties files (more stable)
try {
return kuneConstants.getString(I18nUtils.convertMethodName(text, noteForTranslators));
} catch (final MissingResourceException e) {
// Ok this concrete translation is not yet available, we use DB
return getTransFromBD(text, noteForTranslators, encodeText);
}
} else {
return getTransFromBD(text, noteForTranslators, encodeText);
}
}
}