package org.jtheque.i18n.impl; import org.jtheque.i18n.I18NResource; import org.jtheque.i18n.I18NResourceFactory; import org.jtheque.i18n.Internationalizable; import org.jtheque.i18n.LanguageService; import org.jtheque.states.StateService; import org.jtheque.utils.StringUtils; import org.jtheque.utils.SystemProperty; import org.jtheque.utils.annotations.GuardedInternally; import org.jtheque.utils.annotations.ThreadSafe; import org.jtheque.utils.bean.Version; import org.jtheque.utils.collections.CollectionUtils; import org.jtheque.utils.io.CopyException; import org.jtheque.utils.io.FileUtils; import org.slf4j.LoggerFactory; import java.io.File; import java.io.IOException; import java.util.Locale; import java.util.Map; import java.util.Set; /* * Copyright JTheque (Baptiste Wicht) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /* * Copyright JTheque (Baptiste Wicht) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /** * A language service implementation. * * @author Baptiste Wicht */ @ThreadSafe public final class LanguageServiceImpl implements LanguageService { private static final String[] ZERO_LENGTH_ARRAY = new String[0]; @GuardedInternally private final Map<String, String> baseNames = CollectionUtils.newConcurrentMap(10); @GuardedInternally private final Set<Internationalizable> internationalizables = CollectionUtils.newConcurrentSet(); @GuardedInternally private final JThequeResourceBundle resourceBundle = new JThequeResourceBundle(); @GuardedInternally private final LanguageState state; private volatile Locale locale = Locale.getDefault(); private final Object lock = new Object(); private static final Version RESOURCES_VERSION = Version.get("1.0"); /** * Construct a new ResourceManager. * * @param stateService The state service. */ public LanguageServiceImpl(StateService stateService) { super(); state = stateService.getState(new LanguageState()); init(); } /** * Init the language service. */ private void init() { locale = toLocale(state.getLanguage()); Locale.setDefault(locale); registerResource("core_messages", RESOURCES_VERSION, I18NResourceFactory.fromResource(getClass(), "org/jtheque/i18n/messages_de.properties"), I18NResourceFactory.fromResource(getClass(), "org/jtheque/i18n/messages_en.properties"), I18NResourceFactory.fromResource(getClass(), "org/jtheque/i18n/messages_fr.properties")); registerResource("core_dialogs", RESOURCES_VERSION, I18NResourceFactory.fromResource(getClass(), "org/jtheque/i18n/dialogs_de.properties"), I18NResourceFactory.fromResource(getClass(), "org/jtheque/i18n/dialogs_en.properties"), I18NResourceFactory.fromResource(getClass(), "org/jtheque/i18n/dialogs_fr.properties")); registerResource("core_i18n", RESOURCES_VERSION, I18NResourceFactory.fromResource(getClass(), "org/jtheque/i18n/core_de.properties"), I18NResourceFactory.fromResource(getClass(), "org/jtheque/i18n/core_en.properties"), I18NResourceFactory.fromResource(getClass(), "org/jtheque/i18n/core_fr.properties")); registerResource("core_errors", RESOURCES_VERSION, I18NResourceFactory.fromResource(getClass(), "org/jtheque/i18n/errors_de.properties"), I18NResourceFactory.fromResource(getClass(), "org/jtheque/i18n/errors_en.properties"), I18NResourceFactory.fromResource(getClass(), "org/jtheque/i18n/errors_fr.properties")); } @Override public void registerResource(String name, Version version, I18NResource... resources) { synchronized (this) { Version previousVersion = state.getResourceVersion(name); File folder = new File(SystemProperty.USER_DIR.get(), "core/i18n"); FileUtils.createIfNotExists(folder); if (previousVersion == null || version.isGreaterThan(previousVersion)) { state.setResourceVersion(name, version); for (I18NResource resource : resources) { File resourceFile = new File(folder, resource.getFileName()); FileUtils.delete(resourceFile); try { FileUtils.copy(resource.getResource().getInputStream(), resourceFile); } catch (CopyException e) { LoggerFactory.getLogger(getClass()).error("Unable to copy resource {}", resource); LoggerFactory.getLogger(getClass()).error("Cause exception", e); } catch (IOException e) { LoggerFactory.getLogger(getClass()).error("Unable to copy resource {}", resource); LoggerFactory.getLogger(getClass()).error("Cause exception", e); } } } String baseName = "file:" + folder + '/' + getBaseName(resources[0]); resourceBundle.addBaseName(baseName); baseNames.put(name, baseName); } } @Override public void releaseResource(String name) { if (baseNames.containsKey(name)) { resourceBundle.removeBaseName(baseNames.get(name)); } } @Override public void setCurrentLanguage(String language) { String shortForm = toShortForm(language); synchronized (lock) { state.setLanguage(shortForm); setCurrentLocale(toLocale(shortForm)); } } /** * Set the current locale. * * @param locale The new current locale. */ private void setCurrentLocale(Locale locale) { this.locale = locale; Locale.setDefault(locale); refreshAll(); } @Override public void refreshAll() { for (Internationalizable internationalizable : internationalizables) { internationalizable.refreshText(this); } } @Override public void addInternationalizable(Internationalizable internationalizable) { internationalizables.add(internationalizable); } @Override public Locale getCurrentLocale() { return locale; } @Override public String getCurrentLanguage() { return StringUtils.setFirstLetterUpper(locale.getDisplayLanguage()); } @Override public String[] getLinesMessage(String key) { String message = getMessage(key); if (StringUtils.isEmpty(message)) { return ZERO_LENGTH_ARRAY; } return StringUtils.getLines(message); } @Override public String getMessage(String key, Object... replaces) { if (StringUtils.isEmpty(key)) { return StringUtils.EMPTY_STRING; } return resourceBundle.getMessage(key, replaces, locale); } /** * Return the base name of the i18n resource. * * @param resource The resource to compute the base name for. * * @return The base name of the i18n resource. */ private static String getBaseName(I18NResource resource) { String name = resource.getFileName(); if (name.contains("_")) { return name.substring(0, name.indexOf('_')); } return name.substring(0, name.indexOf('.')); } /** * Convert the language to his short form. * * @param language The language to convert. * * @return The short form of the language. */ private static String toShortForm(CharSequence language) { if ("Français".equals(language) || "fr".equals(language)) { return "fr"; } else if ("Deutsch".equals(language) || "de".equals(language)) { return "de"; } return "en"; } /** * Transform the short language from to a Locale instance. * * @param shortForm The short language form. * * @return The Locale corresponding to the short form. If the short don't correspond to any Locale, this method * return the english Locale. */ private static Locale toLocale(String shortForm) { if ("fr".equals(shortForm)) { return Locale.FRENCH; } else if ("de".equals(shortForm)) { return Locale.GERMAN; } else if ("en".equals(shortForm)) { return Locale.ENGLISH; } LoggerFactory.getLogger(LanguageServiceImpl.class).error("Unable to get the locale"); return Locale.ENGLISH; } }