Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[knx] Improve localization #13293

Merged
merged 5 commits into from
Oct 11, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
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;
Expand Down Expand Up @@ -289,9 +290,8 @@ private void disconnect(@Nullable Exception e) {
private synchronized void disconnect(@Nullable Exception e, Optional<ThingStatusDetail> 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);
}
Expand Down Expand Up @@ -406,8 +406,9 @@ public void linkClosed(@Nullable CloseEvent closeEvent) {
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();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,10 @@
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;
Expand All @@ -29,6 +32,7 @@
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;

Expand All @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
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;
Expand Down Expand Up @@ -164,7 +165,8 @@ private void pollDeviceStatus() {
} 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));
}
}

Expand Down Expand Up @@ -194,7 +196,8 @@ protected void attachToClient() {
} 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();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
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;
Expand Down Expand Up @@ -87,11 +88,12 @@ public void initializeLater() {
} 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;
}

Expand Down Expand Up @@ -120,15 +122,15 @@ public void initializeLater() {
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)
&& (secureTunnel.userKey.length == 16));
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;
}

Expand All @@ -150,22 +152,21 @@ public void initializeLater() {
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;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -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 {
jlaur marked this conversation as resolved.
Show resolved Hide resolved
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 <description (translated)> (<class name>, <e.getLocalizedMessage (not
* translated)>), 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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Loading