/** * OLAT - Online Learning and Training<br> * http://www.olat.org * <p> * Licensed under the Apache License, Version 2.0 (the "License"); <br> * you may not use this file except in compliance with the License.<br> * You may obtain a copy of the License at * <p> * http://www.apache.org/licenses/LICENSE-2.0 * <p> * Unless required by applicable law or agreed to in writing,<br> * software distributed under the License is distributed on an "AS IS" BASIS, <br> * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br> * See the License for the specific language governing permissions and <br> * limitations under the License. * <p> * Copyright (c) since 2004 at Multimedia- & E-Learning Services (MELS),<br> * University of Zurich, Switzerland. * <hr> * <a href="http://www.openolat.org"> * OpenOLAT - Online Learning and Training</a><br> * This file has been modified by the OpenOLAT community. Changes are licensed * under the Apache 2.0 license as the original file. * <p> */ package org.olat.core.gui.translator; import java.io.PrintWriter; import java.io.StringWriter; import java.io.Writer; import java.util.Locale; import org.apache.log4j.Level; import org.olat.core.helpers.Settings; import org.olat.core.logging.OLATRuntimeException; import org.olat.core.logging.OLog; import org.olat.core.logging.Tracing; import org.olat.core.util.i18n.I18nManager; import org.olat.core.util.i18n.I18nModule; /** * @author Felix Jost */ public class PackageTranslator implements Translator { private static final OLog log = Tracing.createLoggerFor(PackageTranslator.class); private final boolean fallBack; private Translator fallBackTranslator; private final String packageName; private Locale locale; private int fallBackLevel = 0; private PackageTranslator(String packageName, Locale locale, boolean fallBack, Translator fallBackTranslator) { this.locale = locale; this.packageName = packageName; this.fallBackTranslator = fallBackTranslator; this.fallBack = fallBack; } public void setFallBack(PackageTranslator fallback){ this.fallBackTranslator = fallback; } /** * @param packageName * @param locale * @param fallBack */ private PackageTranslator(String packageName, Locale locale, boolean fallBack) { this(packageName, locale, fallBack, null); } /** * cascade two translators * do not use this with multiple cascaded translators! they are lost with this method! * * @param main * @param fallback * @return */ public static Translator cascadeTranslators(PackageTranslator main, Translator fallback){ return new PackageTranslator(main.packageName, main.locale, fallback); } /** * recursively cascade with all fallbacks up to maxDeep levels * @param main * @param fallback * @return */ public Translator cascadeTranslatorsWithAllFallback(PackageTranslator main, Translator fallback){ if (this.fallBackTranslator instanceof PackageTranslator && main.fallBackTranslator != fallback && this.fallBackTranslator != fallback){ PackageTranslator tempTrans = (PackageTranslator) this.fallBackTranslator; PackageTranslator oldPos = this; int maxDeep = 4; while (tempTrans != null && maxDeep > 0) { oldPos = tempTrans; tempTrans = (PackageTranslator) tempTrans.fallBackTranslator; maxDeep--; } if (fallback != oldPos.fallBackTranslator && oldPos != oldPos.fallBackTranslator) { oldPos.fallBackTranslator = fallback; } return main; } return cascadeTranslators(main, fallback); } /** * @param packageName only the package use "class.getPackage().getName()" for it! * @param locale * @param fallBackTranslator */ public PackageTranslator(String packageName, Locale locale, Translator fallBackTranslator) { this(packageName, locale, false, fallBackTranslator); } /** * default with fallback mode * * @param packageName only the package use "class.getPackage().getName()" for it! * @param locale */ public PackageTranslator(String packageName, Locale locale) { this(packageName, locale, true); } /** * Translates the string from the packageName localization file. * * @param key The key to translate * @return The internationalized strings */ @Override public String translate(String key) { String val = translate(key, null); return val; } @Override public String translate(String key, String[] args) { return translate(key, args, Level.WARN); } /** * @see org.olat.core.gui.translator.Translator#translate(java.lang.String, * java.lang.String[]) */ @Override public String translate(String key, String[] args, Level missingTranslationLogLevel) { String val = translate(key,args,false ); // if still null -> fallback to default locale (if not in debug mode) if (val == null) { if (Settings.isDebuging()) { val = getErrorMessage(key); } else { // try with fallBackToDefaultLocale val = translate(key, args, true ); } } if (val != null){ fallBackLevel = 0; } // else value got translated or there is at least an error message telling // which key was not found. // Note: val may be null if there is a localstrings file missing in the default language. use the online translation tool to double-check // Error: ! even in default language: missing translation key! if (val == null) { val = getErrorMessage(key); // TODO: 13.02.2009 Workaround to fix shibboleth-attribute WARN : 'no translation ... in org.olat.course.condition...' if (!packageName.startsWith("org.olat.course.condition")) { if (missingTranslationLogLevel!=null && !missingTranslationLogLevel.equals(Level.OFF)) { if (missingTranslationLogLevel.equals(Level.ERROR)) { log.error(val); } else if (missingTranslationLogLevel.equals(Level.WARN)) { log.warn(val); } else if (missingTranslationLogLevel.equals(Level.INFO)) { log.info(val); } } } // don't use error message in GUI for production, use key instead (OLAT-5896) if (!Settings.isDebuging()) { val = key; } } return val; } /** * Recursive lookup for a key. Used in translate(String key, String[] args). * Should not be called directly, use translate(String key, String[] args). * Must be public, because the definition is in an interface. * @see org.olat.core.gui.translator.Translator#translate(java.lang.String, java.lang.String[], boolean) */ @Override public String translate(String key, String[] args, boolean fallBackToDefaultLocale) { I18nManager i18n = I18nManager.getInstance(); boolean overlayEnabled = I18nModule.isOverlayEnabled(); String val = i18n.getLocalizedString(packageName, key, args, locale, overlayEnabled, fallBackToDefaultLocale); if (val == null) { // if not found, try the fallBackTranslator if (fallBackTranslator != null && fallBackLevel < 10) { fallBackLevel++; val = fallBackTranslator.translate(key, args, fallBackToDefaultLocale); } else if (fallBack) { // both fallback and fallbacktranslator does not // make sense; latest translator in chain should // fallback to application fallback. val = i18n.getLocalizedString(I18nModule.getApplicationFallbackBundle(), key, args, locale, overlayEnabled, fallBackToDefaultLocale); if (val == null) { // lastly fall back to brasato framework fallback val = i18n.getLocalizedString(I18nModule.getCoreFallbackBundle(), key, args, locale, overlayEnabled, fallBackToDefaultLocale); } } } if (val != null){ fallBackLevel = 0; } return val; } /** * Internal helper to format an error message for the given key * @param key * @return */ private String getErrorMessage(String key) { StringBuilder sb = new StringBuilder(150); sb.append(NO_TRANSLATION_ERROR_PREFIX).append(key); sb.append(", fallBackLevel ").append(fallBackLevel); sb.append(": in ").append(packageName); sb.append(" (fallback:").append(fallBack); // Reset fallBackLevel fallBackLevel = 0; String babel; if (fallBackTranslator instanceof PackageTranslator) { babel = ((PackageTranslator)fallBackTranslator).packageName + " " + fallBackTranslator.toString(); } else { babel = fallBackTranslator == null ? "-" : fallBackTranslator.toString(); } sb.append(", fallBackTranslator:").append(babel); sb.append(") for locale ").append(locale); OLATRuntimeException ore = new OLATRuntimeException("transl dummy",null); //use stracktrace to find out more where the missing translation comes from final Writer result = new StringWriter(); final PrintWriter printWriter = new PrintWriter(result); ore.printStackTrace(printWriter); sb.append(result.toString()); return sb.toString(); } /** * @see org.olat.core.gui.translator.Translator#getLocale() */ public Locale getLocale() { return this.locale; } /** * Not used normally. Sets the locale. Use only if e.g. a translator (which * should then be an instance variable of a controller) is needed in the DMZ * area where no user is logged in yet * * @param locale The locale to set */ @Override public void setLocale(Locale locale) { this.locale = locale; if (fallBackTranslator != null) { fallBackTranslator.setLocale(locale); } } /** * The package of this package translator * @return */ public String getPackageName() { return packageName; } @Override public String toString(){ return "PackageTranslator for package: " + packageName + " is fallback: " + fallBack + " next child if any: \n " + ((this.fallBackTranslator != null && this.fallBackTranslator == this) ? "recurse itself !" : this.fallBackTranslator); } public boolean isStacked(){ return this.fallBackTranslator != null; } }