diff --git a/guacamole-common/src/main/java/org/apache/guacamole/protocol/ConfiguredGuacamoleSocket.java b/guacamole-common/src/main/java/org/apache/guacamole/protocol/ConfiguredGuacamoleSocket.java index cf43b68f1..450cef7f5 100644 --- a/guacamole-common/src/main/java/org/apache/guacamole/protocol/ConfiguredGuacamoleSocket.java +++ b/guacamole-common/src/main/java/org/apache/guacamole/protocol/ConfiguredGuacamoleSocket.java @@ -19,7 +19,6 @@ package org.apache.guacamole.protocol; - import java.util.List; import org.apache.guacamole.GuacamoleException; import org.apache.guacamole.GuacamoleServerException; @@ -56,6 +55,15 @@ public class ConfiguredGuacamoleSocket implements GuacamoleSocket { */ private String id; + /** + * The protocol version that will be used to communicate with guacd. The + * default is 1.0.0, and, if the server does not provide a specific version + * it will be assumed that it operates at this version and certain features + * may be unavailable. + */ + private GuacamoleProtocolVersion protocol = + GuacamoleProtocolVersion.VERSION_1_0_0; + /** * Waits for the instruction having the given opcode, returning that * instruction once it has been read. If the instruction is never read, @@ -142,6 +150,13 @@ public class ConfiguredGuacamoleSocket implements GuacamoleSocket { // Retrieve argument name String arg_name = arg_names.get(i); + + // Check for protocol version as first argument + if (i == 0 && arg_name.startsWith("VERSION_")) { + protocol = GuacamoleProtocolVersion.getVersion(arg_name); + arg_values[i] = protocol.toString(); + continue; + } // Get defined value for name String value = config.getParameter(arg_name); @@ -184,6 +199,19 @@ public class ConfiguredGuacamoleSocket implements GuacamoleSocket { "image", info.getImageMimetypes().toArray(new String[0]) )); + + // Check for support for timezone handshake + if (protocol.isSupported(GuacamoleProtocolCapability.TIMEZONE_HANDSHAKE)) { + // Send client timezone, if available + String timezone = info.getTimezone(); + if (timezone != null) { + writer.writeInstruction( + new GuacamoleInstruction( + "timezone", + info.getTimezone() + )); + } + } // Send args writer.writeInstruction(new GuacamoleInstruction("connect", arg_values)); diff --git a/guacamole-common/src/main/java/org/apache/guacamole/protocol/GuacamoleClientInformation.java b/guacamole-common/src/main/java/org/apache/guacamole/protocol/GuacamoleClientInformation.java index d90d05d3f..6d54a2f6c 100644 --- a/guacamole-common/src/main/java/org/apache/guacamole/protocol/GuacamoleClientInformation.java +++ b/guacamole-common/src/main/java/org/apache/guacamole/protocol/GuacamoleClientInformation.java @@ -58,6 +58,11 @@ public class GuacamoleClientInformation { * The list of image mimetypes reported by the client to be supported. */ private final List imageMimetypes = new ArrayList(); + + /** + * The timezone reported by the client. + */ + private String timezone; /** * Returns the optimal screen width requested by the client, in pixels. @@ -144,5 +149,31 @@ public class GuacamoleClientInformation { public List getImageMimetypes() { return imageMimetypes; } + + /** + * Return the timezone as reported by the client, or null if the timezone + * is not set. Valid timezones are specified in IANA zone key format, + * also known as Olson time zone database or TZ Database. + * + * @return + * A string value of the timezone reported by the client. + */ + public String getTimezone() { + return timezone; + } + + /** + * Set the string value of the timezone, or null if the timezone will not + * be provided by the client. Valid timezones are specified in IANA zone + * key format (aka Olson time zone database or tz database). + * + * @param timezone + * The string value of the timezone reported by the client, in tz + * database format, or null if the timezone is not provided by the + * client. + */ + public void setTimezone(String timezone) { + this.timezone = timezone; + } } diff --git a/guacamole-common/src/main/java/org/apache/guacamole/protocol/GuacamoleProtocolCapability.java b/guacamole-common/src/main/java/org/apache/guacamole/protocol/GuacamoleProtocolCapability.java new file mode 100644 index 000000000..6d4bdd169 --- /dev/null +++ b/guacamole-common/src/main/java/org/apache/guacamole/protocol/GuacamoleProtocolCapability.java @@ -0,0 +1,77 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + +package org.apache.guacamole.protocol; + +/** + * An enum that specifies protocol capabilities that can be used to help + * detect whether or not a particular protocol version contains a capability. + */ +public enum GuacamoleProtocolCapability { + + /** + * Whether or not the protocol supports arbitrary ordering of the + * handshake instructions. This was introduced in VERSION_1_1_0. + */ + ARBITRARY_HANDSHAKE_ORDER(GuacamoleProtocolVersion.VERSION_1_1_0), + + /** + * Whether or not the protocol supports the ability to dynamically + * detect the version client and server are running in order to allow + * compatibility between differing client and server versions. This + * was introduced in VERSION_1_1_0. + */ + PROTOCOL_VERSION_DETECTION(GuacamoleProtocolVersion.VERSION_1_1_0), + + /** + * Whether or not the protocol supports the timezone instruction during + * the Client-Server handshake phase. This was introduced in + * VERSION_1_1_0. + */ + TIMEZONE_HANDSHAKE(GuacamoleProtocolVersion.VERSION_1_1_0); + + /** + * The minimum protocol version required to support this capability. + */ + private final GuacamoleProtocolVersion version; + + /** + * Create a new enum value with the given protocol version as the minimum + * required to support the capability. + * + * @param version + * The minimum required protocol version for supporting the + * capability. + */ + GuacamoleProtocolCapability(GuacamoleProtocolVersion version) { + this.version = version; + } + + /** + * Returns the minimum protocol version required to support this + * capability. + * + * @return + * The minimum protocol version required to support this capability. + */ + public GuacamoleProtocolVersion getVersion() { + return version; + } + +} diff --git a/guacamole-common/src/main/java/org/apache/guacamole/protocol/GuacamoleProtocolVersion.java b/guacamole-common/src/main/java/org/apache/guacamole/protocol/GuacamoleProtocolVersion.java new file mode 100644 index 000000000..3b38c427a --- /dev/null +++ b/guacamole-common/src/main/java/org/apache/guacamole/protocol/GuacamoleProtocolVersion.java @@ -0,0 +1,250 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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. + */ + +package org.apache.guacamole.protocol; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * An enum that defines the available Guacamole protocol versions that can be + * used between guacd and clients, and provides convenience methods for parsing + * and comparing versions. + */ +public enum GuacamoleProtocolVersion { + + /** + * Protocol version 1.0.0 and older. Any client that doesn't explicitly + * set the protocol version will negotiate down to this protocol version. + * This requires that handshake instructions be ordered correctly, and + * lacks support for certain protocol-related features introduced in later + * versions. + */ + VERSION_1_0_0(1, 0, 0), + + /** + * Protocol version 1.1.0, which introduces Client-Server version + * detection, arbitrary handshake instruction order, and support + * for passing the client timezone to the server during the handshake. + */ + VERSION_1_1_0(1, 1, 0); + + /** + * A regular expression that matches the VERSION_X_Y_Z pattern, where + * X is the major version component, Y is the minor version component, + * and Z is the patch version component. This expression puts each of + * the version components in their own group so that they can be easily + * used later. + */ + private static final Pattern VERSION_PATTERN = + Pattern.compile("^VERSION_([0-9]+)_([0-9]+)_([0-9]+)$"); + + /** + * The major version component of the protocol version. + */ + private final int major; + + /** + * The minor version component of the protocol version. + */ + private final int minor; + + /** + * The patch version component of the protocol version. + */ + private final int patch; + + /** + * Generate a new GuacamoleProtocolVersion object with the given + * major version, minor version, and patch version. + * + * @param major + * The integer representation of the major version component. + * + * @param minor + * The integer representation of the minor version component. + * + * @param patch + * The integer representation of the patch version component. + */ + GuacamoleProtocolVersion(int major, int minor, int patch) { + this.major = major; + this.minor = minor; + this.patch = patch; + } + + /** + * Return the major version component of the protocol version. + * + * @return + * The integer major version component. + */ + public int getMajor() { + return major; + } + + /** + * Return the minor version component of the protocol version. + * + * @return + * The integer minor version component. + */ + public int getMinor() { + return minor; + } + + /** + * Return the patch version component of the protocol version. + * + * @return + * The integer patch version component. + */ + public int getPatch() { + return patch; + } + + /** + * Determines whether or not this object is greater than or equal to the + * the version passed in to the method. Returns a boolean true if the + * version is the same as or greater than the other version, otherwise + * false. + * + * @param otherVersion + * The version to which this object should be compared. + * + * @return + * True if this object is greater than or equal to the other version. + */ + private boolean atLeast(GuacamoleProtocolVersion otherVersion) { + + // If major is not the same, return inequality + if (major != otherVersion.getMajor()) + return this.major > major; + + // Major is the same, but minor is not, return minor inequality + if (minor != otherVersion.getMinor()) + return this.minor > minor; + + // Major and minor are equal, so return patch inequality + return patch >= otherVersion.getPatch(); + + } + + /** + * Compare this version with the major, minor, and patch components + * provided to the method, and determine if this version is compatible + * with the provided version, returning a boolean true if it is compatible, + * otherwise false. This version is compatible with the version specified + * by the provided components if the major, minor, and patch components + * are equivalent or less than those provided. + * + * @param major + * The major version component to compare for compatibility. + * + * @param minor + * The minor version component to compare for compatibility. + * + * @param patch + * The patch version component to compare for compatibility. + * + * @return + * True if this version is compatibility with the version components + * provided, otherwise false. + */ + private boolean isCompatible(int major, int minor, int patch) { + + if (this.major != major) + return this.major < major; + + if (this.minor != minor) + return this.minor < minor; + + return this.patch <= patch; + + } + + /** + * Parse the String format of the version provided and return the + * the enum value matching that version. If no value is provided, return + * null. + * + * @param version + * The String format of the version to parse. + * + * @return + * The enum value that matches the specified version, VERSION_1_0_0 + * if no match is found, or null if no comparison version is provided. + */ + public static GuacamoleProtocolVersion getVersion(String version) { + + // If nothing is passed in, return null + if (version == null || version.isEmpty()) + return null; + + // Check the string against the pattern matcher + Matcher versionMatcher = VERSION_PATTERN.matcher(version); + + // If there is no RegEx match, return null + if (!versionMatcher.matches()) + return null; + + try { + // Try the valueOf function + return valueOf(version); + + } + + // If nothing matches, find the closest compatible version. + catch (IllegalArgumentException e) { + int myMajor = Integer.parseInt(versionMatcher.group(1)); + int myMinor = Integer.parseInt(versionMatcher.group(2)); + int myPatch = Integer.parseInt(versionMatcher.group(3)); + + GuacamoleProtocolVersion myVersion = VERSION_1_0_0; + + // Loop through possible versions, grabbing the latest compatible + for (GuacamoleProtocolVersion v : values()) { + if (v.isCompatible(myMajor, myMinor, myPatch)) + myVersion = v; + } + + return myVersion; + + } + + } + + /** + * Returns true if the specified capability is supported in the current + * protocol version, otherwise false. + * + * @param capability + * The protocol capability that is being checked for support. + * + * @return + * True if the capability is supported in the current version, + * otherwise false. + */ + public boolean isSupported(GuacamoleProtocolCapability capability) { + + return atLeast(capability.getVersion()); + + } + +} diff --git a/guacamole-ext/src/main/resources/org/apache/guacamole/protocols/rdp.json b/guacamole-ext/src/main/resources/org/apache/guacamole/protocols/rdp.json index cd84142bb..8b1f5a23c 100644 --- a/guacamole-ext/src/main/resources/org/apache/guacamole/protocols/rdp.json +++ b/guacamole-ext/src/main/resources/org/apache/guacamole/protocols/rdp.json @@ -215,6 +215,10 @@ { "name" : "static-channels", "type" : "TEXT" + }, + { + "name" : "timezone", + "type" : "TIMEZONE" } ] }, diff --git a/guacamole-ext/src/main/resources/org/apache/guacamole/protocols/ssh.json b/guacamole-ext/src/main/resources/org/apache/guacamole/protocols/ssh.json index a22647859..7ee4740c7 100644 --- a/guacamole-ext/src/main/resources/org/apache/guacamole/protocols/ssh.json +++ b/guacamole-ext/src/main/resources/org/apache/guacamole/protocols/ssh.json @@ -114,9 +114,13 @@ "options" : [ "", "127", "8" ] }, { - "name" : "terminal-type", + "name" : "terminal-type", "type" : "ENUM", "options" : [ "", "xterm", "xterm-256color", "vt220", "vt100", "ansi", "linux" ] + }, + { + "name" : "timezone", + "type" : "TIMEZONE" } ] }, diff --git a/guacamole/pom.xml b/guacamole/pom.xml index 3834b63ba..5552e4b64 100644 --- a/guacamole/pom.xml +++ b/guacamole/pom.xml @@ -501,6 +501,13 @@ 27.0.1-jre + + + org.webjars.npm + jstz + 1.0.10 + + diff --git a/guacamole/src/licenses/LICENSE b/guacamole/src/licenses/LICENSE index f1e93b77d..c3d308d86 100644 --- a/guacamole/src/licenses/LICENSE +++ b/guacamole/src/licenses/LICENSE @@ -614,6 +614,36 @@ licenses; we recommend you read them, as their terms may differ from the terms above. +JSTZ (https://pellepim.bitbucket.io/jstz/) +------------------------------------------ + + Version: 1.0.10 + From: 'Jon Nylander' (https://pellepim.bitbucket.io/jstz/) + License(s): + MIT (bundled/jstz-1.0.10/LICENSE) + +Copyright (c) 2012 Jon Nylander, project maintained at +https://bitbucket.org/pellepim/jstimezonedetect + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to +do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + + Logback (http://logback.qos.ch/) -------------------------------- diff --git a/guacamole/src/licenses/bundled/jstz-1.0.10/LICENSE b/guacamole/src/licenses/bundled/jstz-1.0.10/LICENSE new file mode 100644 index 000000000..c48af16c6 --- /dev/null +++ b/guacamole/src/licenses/bundled/jstz-1.0.10/LICENSE @@ -0,0 +1,22 @@ +MIT License + +Copyright (c) 2012 Jon Nylander, project maintained at +https://bitbucket.org/pellepim/jstimezonedetect + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to +do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. \ No newline at end of file diff --git a/guacamole/src/main/java/org/apache/guacamole/tunnel/TunnelRequest.java b/guacamole/src/main/java/org/apache/guacamole/tunnel/TunnelRequest.java index 8c23dabad..74e3b4dcf 100644 --- a/guacamole/src/main/java/org/apache/guacamole/tunnel/TunnelRequest.java +++ b/guacamole/src/main/java/org/apache/guacamole/tunnel/TunnelRequest.java @@ -95,6 +95,11 @@ public abstract class TunnelRequest { * once for each mimetype. */ public static final String IMAGE_PARAMETER = "GUAC_IMAGE"; + + /** + * The name of the parameter specifying the timezone of the client. + */ + public static final String TIMEZONE_PARAMETER = "GUAC_TIMEZONE"; /** * All supported object types that can be used as the destination of a @@ -365,5 +370,16 @@ public abstract class TunnelRequest { public List getImageMimetypes() { return getParameterValues(IMAGE_PARAMETER); } - + + /** + * Returns the tz database value of the timezone declared by the client + * within the tunnel request. + * + * @return + * The tz database value of the timezone parameter as reported by + * the client. + */ + public String getTimezone() { + return getParameter(TIMEZONE_PARAMETER); + } } diff --git a/guacamole/src/main/java/org/apache/guacamole/tunnel/TunnelRequestService.java b/guacamole/src/main/java/org/apache/guacamole/tunnel/TunnelRequestService.java index 1479d8243..9f0fdfaf3 100644 --- a/guacamole/src/main/java/org/apache/guacamole/tunnel/TunnelRequestService.java +++ b/guacamole/src/main/java/org/apache/guacamole/tunnel/TunnelRequestService.java @@ -166,6 +166,11 @@ public class TunnelRequestService { List imageMimetypes = request.getImageMimetypes(); if (imageMimetypes != null) info.getImageMimetypes().addAll(imageMimetypes); + + // Get the timezone value + String timezone = request.getTimezone(); + if (timezone != null & !timezone.isEmpty()) + info.setTimezone(timezone); return info; } diff --git a/guacamole/src/main/webapp/app/client/types/ManagedClient.js b/guacamole/src/main/webapp/app/client/types/ManagedClient.js index 288931175..de679d67f 100644 --- a/guacamole/src/main/webapp/app/client/types/ManagedClient.js +++ b/guacamole/src/main/webapp/app/client/types/ManagedClient.js @@ -42,6 +42,7 @@ angular.module('client').factory('ManagedClient', ['$rootScope', '$injector', var authenticationService = $injector.get('authenticationService'); var connectionGroupService = $injector.get('connectionGroupService'); var connectionService = $injector.get('connectionService'); + var preferenceService = $injector.get('preferenceService'); var requestService = $injector.get('requestService'); var tunnelService = $injector.get('tunnelService'); var guacAudio = $injector.get('guacAudio'); @@ -235,6 +236,7 @@ angular.module('client').factory('ManagedClient', ['$rootScope', '$injector', + "&GUAC_WIDTH=" + Math.floor(optimal_width) + "&GUAC_HEIGHT=" + Math.floor(optimal_height) + "&GUAC_DPI=" + Math.floor(optimal_dpi) + + "&GUAC_TIMEZONE=" + encodeURIComponent(preferenceService.preferences.timezone) + (connectionParameters ? '&' + connectionParameters : ''); // Add audio mimetypes to connect string diff --git a/guacamole/src/main/webapp/app/settings/services/preferenceService.js b/guacamole/src/main/webapp/app/settings/services/preferenceService.js index bcd86336b..9c4d5fc94 100644 --- a/guacamole/src/main/webapp/app/settings/services/preferenceService.js +++ b/guacamole/src/main/webapp/app/settings/services/preferenceService.js @@ -98,6 +98,18 @@ angular.module('settings').provider('preferenceService', ['$injector', return language.replace(/-/g, '_'); }; + + /** + * Return the timezone detected for the current browser session + * by the JSTZ timezone library. + * + * @returns String + * The name of the currently-detected timezone in IANA zone key + * format (Olson time zone database). + */ + var getDetectedTimezone = function getDetectedTimezone() { + return jstz.determine().name(); + }; /** * All currently-set preferences, as name/value pairs. Each property name @@ -128,7 +140,15 @@ angular.module('settings').provider('preferenceService', ['$injector', * * @type String */ - language : getDefaultLanguageKey() + language : getDefaultLanguageKey(), + + /** + * The timezone set by the user, in IANA zone key format (Olson time + * zone database). + * + * @type String + */ + timezone : getDetectedTimezone() }; diff --git a/guacamole/src/main/webapp/app/settings/styles/preferences.css b/guacamole/src/main/webapp/app/settings/styles/preferences.css index ed8460dd4..9a966b552 100644 --- a/guacamole/src/main/webapp/app/settings/styles/preferences.css +++ b/guacamole/src/main/webapp/app/settings/styles/preferences.css @@ -18,7 +18,7 @@ */ .preferences .update-password .form, -.preferences .language .form { +.preferences .locale .form { padding-left: 0.5em; border-left: 3px solid rgba(0, 0, 0, 0.125); } \ No newline at end of file diff --git a/guacamole/src/main/webapp/app/settings/templates/settingsPreferences.html b/guacamole/src/main/webapp/app/settings/templates/settingsPreferences.html index 826a5cd20..8c924539b 100644 --- a/guacamole/src/main/webapp/app/settings/templates/settingsPreferences.html +++ b/guacamole/src/main/webapp/app/settings/templates/settingsPreferences.html @@ -1,8 +1,8 @@
- -
-

{{'SETTINGS_PREFERENCES.HELP_LANGUAGE' | translate}}

+ +
+

{{'SETTINGS_PREFERENCES.HELP_LOCALE' | translate}}

@@ -13,6 +13,15 @@
+ + +
+ + +
diff --git a/guacamole/src/main/webapp/index.html b/guacamole/src/main/webapp/index.html index 1d51606a9..e675546c6 100644 --- a/guacamole/src/main/webapp/index.html +++ b/guacamole/src/main/webapp/index.html @@ -85,6 +85,9 @@ + + + diff --git a/guacamole/src/main/webapp/translations/en.json b/guacamole/src/main/webapp/translations/en.json index 96bb0e084..3bc06a581 100644 --- a/guacamole/src/main/webapp/translations/en.json +++ b/guacamole/src/main/webapp/translations/en.json @@ -419,6 +419,7 @@ "FIELD_HEADER_REMOTE_APP_ARGS" : "Parameters:", "FIELD_HEADER_REMOTE_APP_DIR" : "Working directory:", "FIELD_HEADER_REMOTE_APP" : "Program:", + "FIELD_HEADER_TIMEZONE" : "Timezone:", "FIELD_HEADER_SECURITY" : "Security mode:", "FIELD_HEADER_SERVER_LAYOUT" : "Keyboard layout:", "FIELD_HEADER_SFTP_DIRECTORY" : "Default upload directory:", @@ -770,6 +771,7 @@ "FIELD_HEADER_PASSWORD_OLD" : "Current Password:", "FIELD_HEADER_PASSWORD_NEW" : "New Password:", "FIELD_HEADER_PASSWORD_NEW_AGAIN" : "Confirm New Password:", + "FIELD_HEADER_TIMEZONE" : "Timezone:", "FIELD_HEADER_USERNAME" : "Username:", "HELP_DEFAULT_INPUT_METHOD" : "The default input method determines how keyboard events are received by Guacamole. Changing this setting may be necessary when using a mobile device, or when typing through an IME. This setting can be overridden on a per-connection basis within the Guacamole menu.", @@ -777,7 +779,7 @@ "HELP_INPUT_METHOD_NONE" : "@:CLIENT.HELP_INPUT_METHOD_NONE", "HELP_INPUT_METHOD_OSK" : "@:CLIENT.HELP_INPUT_METHOD_OSK", "HELP_INPUT_METHOD_TEXT" : "@:CLIENT.HELP_INPUT_METHOD_TEXT", - "HELP_LANGUAGE" : "Select a different language below to change the language of all text within Guacamole. Available choices will depend on which languages are installed.", + "HELP_LOCALE" : "Options below are related to the locale of the user and will impact how various parts of the interface are displayed.", "HELP_MOUSE_MODE_ABSOLUTE" : "@:CLIENT.HELP_MOUSE_MODE_ABSOLUTE", "HELP_MOUSE_MODE_RELATIVE" : "@:CLIENT.HELP_MOUSE_MODE_RELATIVE", "HELP_UPDATE_PASSWORD" : "If you wish to change your password, enter your current password and the desired new password below, and click \"Update Password\". The change will take effect immediately.",