From 4e69e17f41849714dfbdd1d7ba03edd83aee5fa0 Mon Sep 17 00:00:00 2001 From: Holger Friedrich Date: Tue, 11 Oct 2022 21:18:17 +0200 Subject: [PATCH] [knx] Improve localization (#13293) * [knx] Improve localization - introduce localization of error messages - add new strings for common exceptions - provide helper functions for translation - add test cases Signed-off-by: Holger Friedrich --- .../internal/client/AbstractKNXClient.java | 7 +- .../internal/factory/KNXHandlerFactory.java | 10 ++ .../handler/AbstractKNXThingHandler.java | 7 +- .../handler/IPBridgeThingHandler.java | 25 ++-- .../internal/i18n/KNXTranslationProvider.java | 128 ++++++++++++++++++ .../main/resources/OH-INF/i18n/knx.properties | 26 ++++ .../i18n/KNXTranslationProviderTest.java | 111 +++++++++++++++ .../internal/i18n/MockedLocaleProvider.java | 30 ++++ .../i18n/MockedTranslationProvider.java | 54 ++++++++ 9 files changed, 381 insertions(+), 17 deletions(-) create mode 100644 bundles/org.openhab.binding.knx/src/main/java/org/openhab/binding/knx/internal/i18n/KNXTranslationProvider.java create mode 100644 bundles/org.openhab.binding.knx/src/test/java/org/openhab/binding/knx/internal/i18n/KNXTranslationProviderTest.java create mode 100644 bundles/org.openhab.binding.knx/src/test/java/org/openhab/binding/knx/internal/i18n/MockedLocaleProvider.java create mode 100644 bundles/org.openhab.binding.knx/src/test/java/org/openhab/binding/knx/internal/i18n/MockedTranslationProvider.java diff --git a/bundles/org.openhab.binding.knx/src/main/java/org/openhab/binding/knx/internal/client/AbstractKNXClient.java b/bundles/org.openhab.binding.knx/src/main/java/org/openhab/binding/knx/internal/client/AbstractKNXClient.java index 397d777fe..36556c59a 100644 --- a/bundles/org.openhab.binding.knx/src/main/java/org/openhab/binding/knx/internal/client/AbstractKNXClient.java +++ b/bundles/org.openhab.binding.knx/src/main/java/org/openhab/binding/knx/internal/client/AbstractKNXClient.java @@ -28,6 +28,7 @@ import org.eclipse.jdt.annotation.Nullable; import org.openhab.binding.knx.internal.KNXTypeMapper; import org.openhab.binding.knx.internal.dpt.KNXCoreTypeMapper; import org.openhab.binding.knx.internal.handler.GroupAddressListener; +import org.openhab.binding.knx.internal.i18n.KNXTranslationProvider; import org.openhab.core.thing.ThingStatus; import org.openhab.core.thing.ThingStatusDetail; import org.openhab.core.thing.ThingUID; @@ -289,9 +290,8 @@ public abstract class AbstractKNXClient implements NetworkLinkListener, KNXClien private synchronized void disconnect(@Nullable Exception e, Optional detail) { releaseConnection(); if (e != null) { - final String message = e.getLocalizedMessage(); statusUpdateCallback.updateStatus(ThingStatus.OFFLINE, detail.orElse(ThingStatusDetail.COMMUNICATION_ERROR), - message != null ? message : ""); + KNXTranslationProvider.I18N.getLocalizedException(e)); } else { statusUpdateCallback.updateStatus(ThingStatus.OFFLINE); } @@ -406,8 +406,9 @@ public abstract class AbstractKNXClient implements NetworkLinkListener, KNXClien return; } if (!link.isOpen() && CloseEvent.USER_REQUEST != closeEvent.getInitiator()) { + final String reason = closeEvent.getReason(); statusUpdateCallback.updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, - closeEvent.getReason()); + KNXTranslationProvider.I18N.get(reason)); logger.debug("KNX link has been lost (reason: {} on object {})", closeEvent.getReason(), closeEvent.getSource().toString()); scheduleReconnectJob(); diff --git a/bundles/org.openhab.binding.knx/src/main/java/org/openhab/binding/knx/internal/factory/KNXHandlerFactory.java b/bundles/org.openhab.binding.knx/src/main/java/org/openhab/binding/knx/internal/factory/KNXHandlerFactory.java index 3539144e9..913786aee 100644 --- a/bundles/org.openhab.binding.knx/src/main/java/org/openhab/binding/knx/internal/factory/KNXHandlerFactory.java +++ b/bundles/org.openhab.binding.knx/src/main/java/org/openhab/binding/knx/internal/factory/KNXHandlerFactory.java @@ -20,7 +20,10 @@ import java.util.Collection; import org.openhab.binding.knx.internal.handler.DeviceThingHandler; import org.openhab.binding.knx.internal.handler.IPBridgeThingHandler; import org.openhab.binding.knx.internal.handler.SerialBridgeThingHandler; +import org.openhab.binding.knx.internal.i18n.KNXTranslationProvider; import org.openhab.core.config.core.Configuration; +import org.openhab.core.i18n.LocaleProvider; +import org.openhab.core.i18n.TranslationProvider; import org.openhab.core.net.NetworkAddressService; import org.openhab.core.thing.Bridge; import org.openhab.core.thing.Thing; @@ -29,6 +32,7 @@ import org.openhab.core.thing.ThingUID; import org.openhab.core.thing.binding.BaseThingHandlerFactory; import org.openhab.core.thing.binding.ThingHandler; import org.openhab.core.thing.binding.ThingHandlerFactory; +import org.osgi.service.component.annotations.Activate; import org.osgi.service.component.annotations.Component; import org.osgi.service.component.annotations.Reference; @@ -46,6 +50,12 @@ public class KNXHandlerFactory extends BaseThingHandlerFactory { private NetworkAddressService networkAddressService; + @Activate + public KNXHandlerFactory(final @Reference TranslationProvider translationProvider, + final @Reference LocaleProvider localeProvider) { + KNXTranslationProvider.I18N.setProvider(localeProvider, translationProvider); + } + @Override public boolean supportsThingType(ThingTypeUID thingTypeUID) { return SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID); diff --git a/bundles/org.openhab.binding.knx/src/main/java/org/openhab/binding/knx/internal/handler/AbstractKNXThingHandler.java b/bundles/org.openhab.binding.knx/src/main/java/org/openhab/binding/knx/internal/handler/AbstractKNXThingHandler.java index 27dcef9e4..913e91480 100644 --- a/bundles/org.openhab.binding.knx/src/main/java/org/openhab/binding/knx/internal/handler/AbstractKNXThingHandler.java +++ b/bundles/org.openhab.binding.knx/src/main/java/org/openhab/binding/knx/internal/handler/AbstractKNXThingHandler.java @@ -25,6 +25,7 @@ import org.openhab.binding.knx.internal.client.DeviceInspector; import org.openhab.binding.knx.internal.client.DeviceInspector.Result; import org.openhab.binding.knx.internal.client.KNXClient; import org.openhab.binding.knx.internal.config.DeviceConfig; +import org.openhab.binding.knx.internal.i18n.KNXTranslationProvider; import org.openhab.core.thing.Bridge; import org.openhab.core.thing.Thing; import org.openhab.core.thing.ThingStatus; @@ -164,7 +165,8 @@ public abstract class AbstractKNXThingHandler extends BaseThingHandler implement } catch (KNXException e) { logger.debug("An error occurred while testing the reachability of a thing '{}': {}", getThing().getUID(), e.getMessage()); - updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getLocalizedMessage()); + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, + KNXTranslationProvider.I18N.getLocalizedException(e)); } } @@ -194,7 +196,8 @@ public abstract class AbstractKNXThingHandler extends BaseThingHandler implement } catch (KNXFormatException e) { logger.debug("An exception occurred while setting the individual address '{}': {}", config.getAddress(), e.getMessage()); - updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, e.getLocalizedMessage()); + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, + KNXTranslationProvider.I18N.getLocalizedException(e)); } getClient().registerGroupAddressListener(this); scheduleReadJobs(); diff --git a/bundles/org.openhab.binding.knx/src/main/java/org/openhab/binding/knx/internal/handler/IPBridgeThingHandler.java b/bundles/org.openhab.binding.knx/src/main/java/org/openhab/binding/knx/internal/handler/IPBridgeThingHandler.java index 83f0e0fc0..b362cc3da 100644 --- a/bundles/org.openhab.binding.knx/src/main/java/org/openhab/binding/knx/internal/handler/IPBridgeThingHandler.java +++ b/bundles/org.openhab.binding.knx/src/main/java/org/openhab/binding/knx/internal/handler/IPBridgeThingHandler.java @@ -23,6 +23,7 @@ import org.openhab.binding.knx.internal.client.IPClient; import org.openhab.binding.knx.internal.client.KNXClient; import org.openhab.binding.knx.internal.client.NoOpClient; import org.openhab.binding.knx.internal.config.IPBridgeConfiguration; +import org.openhab.binding.knx.internal.i18n.KNXTranslationProvider; import org.openhab.core.net.NetworkAddressService; import org.openhab.core.thing.Bridge; import org.openhab.core.thing.ThingStatus; @@ -87,11 +88,12 @@ public class IPBridgeThingHandler extends KNXBridgeBaseThingHandler { } catch (KnxSecureException e) { logger.debug("{}, {}", thing.getUID(), e.toString()); - String message = e.getLocalizedMessage(); - if (message == null) { - message = e.getClass().getSimpleName(); + Throwable cause = e.getCause(); + if (cause == null) { + cause = e; } - updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "KNX security: " + message); + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, + KNXTranslationProvider.I18N.getLocalizedException(cause)); return; } @@ -120,7 +122,7 @@ public class IPBridgeThingHandler extends KNXBridgeBaseThingHandler { if (!securityAvailable) { logger.warn("Bridge {} missing security configuration for secure tunnel", thing.getUID()); updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, - "Security configuration missing for secure tunnel"); + "@text/error.knx-secure-tunnel-config-missing"); return; } boolean tunnelOk = ((secureTunnel.user > 0) && (secureTunnel.devKey.length == 16) @@ -128,7 +130,7 @@ public class IPBridgeThingHandler extends KNXBridgeBaseThingHandler { if (!tunnelOk) { logger.warn("Bridge {} incomplete security configuration for secure tunnel", thing.getUID()); updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, - "Security configuration for secure tunnel is incomplete"); + "@text/error.knx-secure-tunnel-config-incomplete"); return; } @@ -150,22 +152,21 @@ public class IPBridgeThingHandler extends KNXBridgeBaseThingHandler { if (!securityAvailable) { logger.warn("Bridge {} missing security configuration for secure routing", thing.getUID()); updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, - "Security configuration missing for secure routing"); + "@text/error.knx-secure-routing-config-missing"); return; } if (secureRouting.backboneGroupKey.length != 16) { // failed to read shared backbone group key from config - logger.warn("Bridge {} missing security configuration for secure routing", thing.getUID()); + logger.warn("Bridge {} invalid security configuration for secure routing", thing.getUID()); updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, - "backboneGroupKey required for secure routing; please configure"); + "@text/error.knx-secure-routing-backbonegroupkey-invalid"); return; } logger.debug("KNX secure routing needs a few seconds to establish connection"); } else { logger.debug("Bridge {} unknown connection type", thing.getUID()); - updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, MessageFormat.format( - "Unknown IP connection type {0}. Known types are either 'TUNNEL', 'ROUTER', 'SECURETUNNEL', or 'SECUREROUTER'", - connectionTypeString)); + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, + MessageFormat.format("@text/knx-unknown-ip-connection-type", connectionTypeString)); return; } diff --git a/bundles/org.openhab.binding.knx/src/main/java/org/openhab/binding/knx/internal/i18n/KNXTranslationProvider.java b/bundles/org.openhab.binding.knx/src/main/java/org/openhab/binding/knx/internal/i18n/KNXTranslationProvider.java new file mode 100644 index 000000000..47b504b83 --- /dev/null +++ b/bundles/org.openhab.binding.knx/src/main/java/org/openhab/binding/knx/internal/i18n/KNXTranslationProvider.java @@ -0,0 +1,128 @@ +/** + * Copyright (c) 2010-2022 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.knx.internal.i18n; + +import java.text.MessageFormat; +import java.util.Locale; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.core.i18n.LocaleProvider; +import org.openhab.core.i18n.TranslationProvider; +import org.osgi.framework.Bundle; +import org.osgi.framework.FrameworkUtil; + +/** + * This class provides translations. It is a helper class for i18n / localization efforts. + * + * @implNote It is implemented as a static singleton, enforced by the single-element enum pattern. + * @apiNote @set() must be called to provide tanslation service, otherwise all functions will return untranslated text. + * Thread safety is ensured. + * @author Holger Friedrich - Initial contribution + * + */ +@NonNullByDefault +public enum KNXTranslationProvider { + I18N; + + private @Nullable LocaleProvider localeProvider; + private @Nullable TranslationProvider translationProvider; + private Bundle bundle; + + private KNXTranslationProvider() { + localeProvider = null; + translationProvider = null; + bundle = FrameworkUtil.getBundle(this.getClass()); + } + + /** + * get translated text + * + * @param text text to be translated, may contain placeholders \{n\} for the n-th optional argument of this function + * @param arguments any optional arguments, will be inserted + * @return translated text with subsitutions if translationprovide is set and provides a translation, otherwise + * returns original text with substitutions + */ + public String get(final String text, @Nullable Object @Nullable... arguments) { + // ensure thread safety: calls to set(..) should not lead to race condition + final TranslationProvider translationProvider = this.translationProvider; + final LocaleProvider localeProvider = this.localeProvider; + if (translationProvider != null) { + // localeProvider might be null, but if not, getLocale will return NonNull Locale + // locale cannot be cached, as getLocale() will return different result once locale is changed by user + final Locale locale = (localeProvider != null) ? localeProvider.getLocale() : Locale.getDefault(); + final String res = translationProvider.getText(bundle, text, text, locale, arguments); + if (res != null) { + return res; + } + } + // translating not possible, we still have the original text without any subsititutions + if (arguments == null || arguments.length == 0) { + return text; + } + // else execute pattern substitution in untranslated text + return MessageFormat.format(text, arguments); + } + + /** + * get exception in user readable (and possibly localized) form + * + * @param e any exception + * @return localized message in form (, ), empty string for null. May possibly change in further releases. + */ + public String getLocalizedException(final Throwable e) { + StringBuffer res = new StringBuffer(); + final String exName = e.getClass().getSimpleName(); + final String key = "exception." + exName; + final String translatedDescription = KNXTranslationProvider.I18N.get(key); + Boolean foundTranslation = !key.equals(translatedDescription); + // detailed message cannot be translated, e.getLocalizedMessage will likely return English + String detail = e.getLocalizedMessage(); + if (detail == null) { + detail = ""; + } + + if (foundTranslation) { + res.append(translatedDescription); + res.append(" ("); + res.append(exName); + if (!detail.isBlank()) { + res.append(", "); + res.append(detail); + } + res.append(")"); + } else { + res.append(exName); + if (!detail.isBlank()) { + res.append(", "); + res.append(detail); + } + } + return res.toString(); + } + + /** + * Set translation providers. To be called to make any translation work. + * + * @param localeProvider openHAB locale provider, can be generated via \@Activate / \@Reference LocaleProvider in + * handler factory + * @param translationProvider openHAB locale provider, can be generated via \@Activate / \@Reference + * TranslationProvider in handler factory + */ + public void setProvider(@Nullable LocaleProvider localeProvider, + @Nullable TranslationProvider translationProvider) { + this.localeProvider = localeProvider; + this.translationProvider = translationProvider; + } +} diff --git a/bundles/org.openhab.binding.knx/src/main/resources/OH-INF/i18n/knx.properties b/bundles/org.openhab.binding.knx/src/main/resources/OH-INF/i18n/knx.properties index 631a9bb7a..93efd9916 100644 --- a/bundles/org.openhab.binding.knx/src/main/resources/OH-INF/i18n/knx.properties +++ b/bundles/org.openhab.binding.knx/src/main/resources/OH-INF/i18n/knx.properties @@ -151,3 +151,29 @@ channel-type.config.knx.single.ga.description = The group address(es) in Group A thing-type.config.knx.serial.group.knxsecure.label = KNX secure thing-type.config.knx.serial.group.knxsecure.description = Settings for KNX secure. Requires KNX secure features to be active in KNX installation. + +# binding specific strings + +error.knx-secure-routing-backbonegroupkey-invalid = backbonegroupkey invalid, please check if it is specified correctly +error.knx-secure-routing-config-missing = Security configuration missing for secure routing +error.knx-secure-tunnel-config-incomplete = Security configuration for secure tunnel is incomplete +error.knx-secure-tunnel-config-missing = Security configuration for secure tunnel is missing +error.knx-unknown-ip-connection-type = Unknown IP connection type: {0}. Known types are either 'TUNNEL', 'ROUTER', 'SECURETUNNEL', or 'SECUREROUTER'. + +# user readable description of exceptions + +exception.KNXAckTimeoutException = Communication timeout, missing response +exception.KNXConnectionClosedException = Connection closed +exception.KNXDisconnectException = Disconnected +exception.KNXException = KNX exception +exception.KNXFormatException = Data format not valid +exception.KNXIllegalArgumentException = Illegal argument +exception.KNXInvalidResponseException = Invalid response from KNX device +exception.KNXLinkClosedException = Link closed, cannot communicate +exception.KNXMLException = Error processing XML data +exception.KNXPortClosedException = Port closed, cannot communicate +exception.KnxPropertyException = Error accessing KNX property +exception.KNXRemoteException = KNX device indicates error +exception.KnxRuntimeException = Runtime error +exception.KnxSecureException = Error processing KNX secure data +exception.KNXTimeoutException = Communication timeout diff --git a/bundles/org.openhab.binding.knx/src/test/java/org/openhab/binding/knx/internal/i18n/KNXTranslationProviderTest.java b/bundles/org.openhab.binding.knx/src/test/java/org/openhab/binding/knx/internal/i18n/KNXTranslationProviderTest.java new file mode 100644 index 000000000..78a684ad6 --- /dev/null +++ b/bundles/org.openhab.binding.knx/src/test/java/org/openhab/binding/knx/internal/i18n/KNXTranslationProviderTest.java @@ -0,0 +1,111 @@ +/** + * Copyright (c) 2010-2022 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.knx.internal.i18n; + +import static org.junit.jupiter.api.Assertions.*; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.junit.jupiter.api.Test; + +import tuwien.auto.calimero.KNXException; +import tuwien.auto.calimero.link.KNXLinkClosedException; + +/** + * + * @author Holger Friedrich - initial contribution + * + */ +@NonNullByDefault +public class KNXTranslationProviderTest { + static final String UNKNOWN = "unknown text"; + static final String UNKNOWN_PATTERN = "unknown text {0}"; + static final String UNKNOWN_FIVE = "unknown text 5"; + static final String UNKNOWN_NULL = "unknown text null"; + static final String KNX_BINDING_KEY = "binding.knx.name"; + static final String KNX_BINDING_VALUE = "KNX Binding"; + static final String CONN_TYPE_PATTERN_KEY = "error.knx-unknown-ip-connection-type"; + static final String CONN_TYPE_PATTERN_VALUE = "Unknown IP connection type: {0}."; + static final String CONN_TYPE_FIVE_VALUE = "Unknown IP connection type: 5."; + static final String CONN_TYPE_NULL_VALUE = "Unknown IP connection type: null."; + + @Test + public void testGetBeforeInit() { + // initial state, should not crash and preferrably return original strings (w. pattern substitution) + assertEquals(UNKNOWN, KNXTranslationProvider.I18N.get(UNKNOWN)); + assertEquals(UNKNOWN, KNXTranslationProvider.I18N.get(UNKNOWN, 5)); + assertEquals(UNKNOWN_NULL, KNXTranslationProvider.I18N.get(UNKNOWN_PATTERN, null, null)); + assertEquals(UNKNOWN_FIVE, KNXTranslationProvider.I18N.get(UNKNOWN_PATTERN, 5)); + // KNXTranslationProvider.I18N.get(..., null) would cause a compiler warning, + // but using a null object of a defined type, it is possible to invoke with null value + String s = null; + assertEquals(UNKNOWN, KNXTranslationProvider.I18N.get(UNKNOWN, s)); + assertEquals(UNKNOWN_NULL, KNXTranslationProvider.I18N.get(UNKNOWN_PATTERN, s)); + } + + @Test + public void testSetProvider() { + // initial state, should not crash + KNXTranslationProvider.I18N.setProvider(null, null); + assertNotNull(KNXTranslationProvider.I18N.get(UNKNOWN)); + + // use mockup classes with known dictionary + KNXTranslationProvider.I18N.setProvider(new MockedLocaleProvider(), new MockedTranslationProvider()); + assertEquals(KNX_BINDING_VALUE, KNXTranslationProvider.I18N.get(KNX_BINDING_KEY)); + assertEquals(CONN_TYPE_FIVE_VALUE, KNXTranslationProvider.I18N.get(CONN_TYPE_PATTERN_KEY, 5)); + assertEquals(UNKNOWN, KNXTranslationProvider.I18N.get(UNKNOWN)); + assertEquals(UNKNOWN, KNXTranslationProvider.I18N.get(UNKNOWN, 5)); + assertEquals(UNKNOWN_NULL, KNXTranslationProvider.I18N.get(UNKNOWN_PATTERN, null, null)); + assertEquals(UNKNOWN_FIVE, KNXTranslationProvider.I18N.get(UNKNOWN_PATTERN, 5)); + // KNXTranslationProvider.I18N.get(..., null) would cause a compiler warning, + // but using a null object of a defined type, it is possible to invoke with null value + String s = null; + assertEquals(UNKNOWN, KNXTranslationProvider.I18N.get(UNKNOWN, s)); + assertEquals(UNKNOWN_NULL, KNXTranslationProvider.I18N.get(UNKNOWN_PATTERN, s)); + + assertEquals(CONN_TYPE_NULL_VALUE, KNXTranslationProvider.I18N.get(CONN_TYPE_PATTERN_KEY, s)); + assertEquals(CONN_TYPE_PATTERN_VALUE, KNXTranslationProvider.I18N.get(CONN_TYPE_PATTERN_KEY)); + + // no locale, should work as fallback to default locale + KNXTranslationProvider.I18N.setProvider(null, new MockedTranslationProvider()); + assertEquals(KNXTranslationProvider.I18N.get(KNX_BINDING_KEY), KNX_BINDING_VALUE); + + // no translations, should return initial string + KNXTranslationProvider.I18N.setProvider(new MockedLocaleProvider(), null); + assertEquals(KNX_BINDING_KEY, KNXTranslationProvider.I18N.get(KNX_BINDING_KEY)); + + // initial state, dictionary should be gone + KNXTranslationProvider.I18N.setProvider(null, null); + assertEquals(KNX_BINDING_KEY, KNXTranslationProvider.I18N.get(KNX_BINDING_KEY)); + } + + @Test + public void testGetLocalizedException() { + // initial state, should not crash + KNXTranslationProvider.I18N.setProvider(null, null); + + final Exception e = new KNXException("error 1"); + final Exception se = new KNXLinkClosedException("connection closed", e); + assertNotNull(KNXTranslationProvider.I18N.getLocalizedException(e)); + assertNotNull(KNXTranslationProvider.I18N.getLocalizedException(se)); + assertEquals("KNXException, error 1", KNXTranslationProvider.I18N.getLocalizedException(e)); + + // use mockup classes with known dictionary + KNXTranslationProvider.I18N.setProvider(new MockedLocaleProvider(), new MockedTranslationProvider()); + // exception which is not part off the dictionary + assertEquals("KNXLinkClosedException, connection closed", + KNXTranslationProvider.I18N.getLocalizedException(se)); + // exception which can be translated + assertEquals("Translated KNX Exception (KNXException, error 1)", + KNXTranslationProvider.I18N.getLocalizedException(e)); + } +} diff --git a/bundles/org.openhab.binding.knx/src/test/java/org/openhab/binding/knx/internal/i18n/MockedLocaleProvider.java b/bundles/org.openhab.binding.knx/src/test/java/org/openhab/binding/knx/internal/i18n/MockedLocaleProvider.java new file mode 100644 index 000000000..d247a9b1d --- /dev/null +++ b/bundles/org.openhab.binding.knx/src/test/java/org/openhab/binding/knx/internal/i18n/MockedLocaleProvider.java @@ -0,0 +1,30 @@ +/** + * Copyright (c) 2010-2022 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.knx.internal.i18n; + +import java.util.Locale; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.core.i18n.LocaleProvider; + +/** + * Locale provider for unit tests. + * + * @author Jacob Laursen - Initial contribution + */ +@NonNullByDefault +public class MockedLocaleProvider implements LocaleProvider { + public Locale getLocale() { + return Locale.ENGLISH; + } +} diff --git a/bundles/org.openhab.binding.knx/src/test/java/org/openhab/binding/knx/internal/i18n/MockedTranslationProvider.java b/bundles/org.openhab.binding.knx/src/test/java/org/openhab/binding/knx/internal/i18n/MockedTranslationProvider.java new file mode 100644 index 000000000..7aa550197 --- /dev/null +++ b/bundles/org.openhab.binding.knx/src/test/java/org/openhab/binding/knx/internal/i18n/MockedTranslationProvider.java @@ -0,0 +1,54 @@ +/** + * Copyright (c) 2010-2022 Contributors to the openHAB project + * + * See the NOTICE file(s) distributed with this work for additional + * information. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.openhab.binding.knx.internal.i18n; + +import static java.util.Map.entry; + +import java.text.MessageFormat; +import java.util.Locale; +import java.util.Map; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.core.i18n.TranslationProvider; +import org.osgi.framework.Bundle; + +/** + * Translation provider for unit tests. + * + * @author Jacob Laursen - Initial contribution + * @author Holger Friedrich - Imported from hdpowerview, adapted + */ +@NonNullByDefault +public class MockedTranslationProvider implements TranslationProvider { + + private static final Map TEXTS = Map.ofEntries(entry("binding.knx.name", "KNX Binding"), + entry("error.knx-unknown-ip-connection-type", "Unknown IP connection type: {0}."), + entry("exception.KNXException", "Translated KNX Exception")); + + public MockedTranslationProvider() { + } + + @Nullable + public String getText(@Nullable Bundle bundle, @Nullable String key, @Nullable String defaultText, + @Nullable Locale locale) { + return ""; + } + + @Nullable + public String getText(@Nullable Bundle bundle, @Nullable String key, @Nullable String defaultText, + @Nullable Locale locale, @Nullable Object @Nullable... arguments) { + String text = TEXTS.get(key); + return MessageFormat.format(text != null ? text : key, arguments); + } +}