GUACAMOLE-1001: Merge changes adding missing attributes to RADIUS requests.

diff --git a/extensions/guacamole-auth-duo/src/main/java/org/apache/guacamole/auth/duo/UserVerificationService.java b/extensions/guacamole-auth-duo/src/main/java/org/apache/guacamole/auth/duo/UserVerificationService.java
index 777c96b..abcb486 100644
--- a/extensions/guacamole-auth-duo/src/main/java/org/apache/guacamole/auth/duo/UserVerificationService.java
+++ b/extensions/guacamole-auth-duo/src/main/java/org/apache/guacamole/auth/duo/UserVerificationService.java
@@ -22,16 +22,16 @@
 import com.google.inject.Inject;
 import java.util.Collections;
 import javax.servlet.http.HttpServletRequest;
-import org.apache.guacamole.GuacamoleClientException;
 import org.apache.guacamole.GuacamoleException;
 import org.apache.guacamole.auth.duo.api.DuoService;
 import org.apache.guacamole.auth.duo.conf.ConfigurationService;
 import org.apache.guacamole.auth.duo.form.DuoSignedResponseField;
 import org.apache.guacamole.form.Field;
+import org.apache.guacamole.language.TranslatableGuacamoleClientException;
+import org.apache.guacamole.language.TranslatableGuacamoleInsufficientCredentialsException;
 import org.apache.guacamole.net.auth.AuthenticatedUser;
 import org.apache.guacamole.net.auth.Credentials;
 import org.apache.guacamole.net.auth.credentials.CredentialsInfo;
-import org.apache.guacamole.net.auth.credentials.GuacamoleInsufficientCredentialsException;
 
 /**
  * Service for verifying the identity of a user against Duo.
@@ -95,14 +95,18 @@
                         Collections.singletonList(signedResponseField));
 
             // Request additional credentials
-            throw new GuacamoleInsufficientCredentialsException(
-                    "LOGIN.INFO_DUO_AUTH_REQUIRED", expectedCredentials);
+            throw new TranslatableGuacamoleInsufficientCredentialsException(
+                    "Verification using Duo is required before authentication "
+                    + "can continue.", "LOGIN.INFO_DUO_AUTH_REQUIRED",
+                    expectedCredentials);
 
         }
 
         // If signed response does not verify this user's identity, abort auth
         if (!duoService.isValidSignedResponse(authenticatedUser, signedResponse))
-            throw new GuacamoleClientException("LOGIN.INFO_DUO_VALIDATION_CODE_INCORRECT");
+            throw new TranslatableGuacamoleClientException("Provided Duo "
+                    + "validation code is incorrect.",
+                    "LOGIN.INFO_DUO_VALIDATION_CODE_INCORRECT");
 
     }
 
diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/JDBCAuthenticationProviderService.java b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/JDBCAuthenticationProviderService.java
index ff605b9..646bf20 100644
--- a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/JDBCAuthenticationProviderService.java
+++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/JDBCAuthenticationProviderService.java
@@ -21,7 +21,6 @@
 
 import com.google.inject.Inject;
 import com.google.inject.Provider;
-import org.apache.guacamole.GuacamoleClientException;
 import org.apache.guacamole.GuacamoleException;
 import org.apache.guacamole.auth.jdbc.security.PasswordPolicyService;
 import org.apache.guacamole.auth.jdbc.sharing.user.SharedAuthenticatedUser;
@@ -29,6 +28,7 @@
 import org.apache.guacamole.auth.jdbc.user.ModeledUser;
 import org.apache.guacamole.auth.jdbc.user.ModeledUserContext;
 import org.apache.guacamole.auth.jdbc.user.UserService;
+import org.apache.guacamole.language.TranslatableGuacamoleClientException;
 import org.apache.guacamole.net.auth.AuthenticatedUser;
 import org.apache.guacamole.net.auth.AuthenticationProvider;
 import org.apache.guacamole.net.auth.Credentials;
@@ -104,11 +104,15 @@
 
                 // Verify user account is still valid as of today
                 if (!user.isAccountValid())
-                    throw new GuacamoleClientException("LOGIN.ERROR_NOT_VALID");
+                    throw new TranslatableGuacamoleClientException("User "
+                            + "account is no longer valid.",
+                            "LOGIN.ERROR_NOT_VALID");
 
                 // Verify user account is allowed to be used at the current time
                 if (!user.isAccountAccessible())
-                    throw new GuacamoleClientException("LOGIN.ERROR_NOT_ACCESSIBLE");
+                    throw new TranslatableGuacamoleClientException("User "
+                            + "account may not be used at this time.",
+                            "LOGIN.ERROR_NOT_ACCESSIBLE");
 
             }
 
diff --git a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/user/UserService.java b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/user/UserService.java
index 0cfe900..a68f082 100644
--- a/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/user/UserService.java
+++ b/extensions/guacamole-auth-jdbc/modules/guacamole-auth-jdbc-base/src/main/java/org/apache/guacamole/auth/jdbc/user/UserService.java
@@ -46,12 +46,13 @@
 import org.apache.guacamole.auth.jdbc.security.PasswordPolicyService;
 import org.apache.guacamole.form.Field;
 import org.apache.guacamole.form.PasswordField;
+import org.apache.guacamole.language.TranslatableGuacamoleClientException;
+import org.apache.guacamole.language.TranslatableGuacamoleInsufficientCredentialsException;
 import org.apache.guacamole.net.auth.ActivityRecord;
 import org.apache.guacamole.net.auth.AuthenticatedUser;
 import org.apache.guacamole.net.auth.AuthenticationProvider;
 import org.apache.guacamole.net.auth.User;
 import org.apache.guacamole.net.auth.credentials.CredentialsInfo;
-import org.apache.guacamole.net.auth.credentials.GuacamoleInsufficientCredentialsException;
 import org.apache.guacamole.net.auth.permission.ObjectPermission;
 import org.apache.guacamole.net.auth.permission.ObjectPermissionSet;
 import org.apache.guacamole.net.auth.permission.SystemPermission;
@@ -494,20 +495,25 @@
         // Require new password if account is expired
         if (newPassword == null || confirmNewPassword == null) {
             logger.info("The password of user \"{}\" has expired and must be reset.", username);
-            throw new GuacamoleInsufficientCredentialsException("LOGIN.INFO_PASSWORD_EXPIRED", EXPIRED_PASSWORD);
+            throw new TranslatableGuacamoleInsufficientCredentialsException("Password has expired",
+                    "LOGIN.INFO_PASSWORD_EXPIRED", EXPIRED_PASSWORD);
         }
 
         // New password must be different from old password
         if (newPassword.equals(credentials.getPassword()))
-            throw new GuacamoleClientException("LOGIN.ERROR_PASSWORD_SAME");
+            throw new TranslatableGuacamoleClientException("New passwords may "
+                    + "not be identical to the current password if password "
+                    + "reset is required.", "LOGIN.ERROR_PASSWORD_SAME");
 
         // New password must not be blank
         if (newPassword.isEmpty())
-            throw new GuacamoleClientException("LOGIN.ERROR_PASSWORD_BLANK");
+            throw new TranslatableGuacamoleClientException("Passwords may not "
+                    + "be blank.", "LOGIN.ERROR_PASSWORD_BLANK");
 
         // Confirm that the password was entered correctly twice
         if (!newPassword.equals(confirmNewPassword))
-            throw new GuacamoleClientException("LOGIN.ERROR_PASSWORD_MISMATCH");
+            throw new TranslatableGuacamoleClientException("New password does "
+                    + "not match.", "LOGIN.ERROR_PASSWORD_MISMATCH");
 
         // Verify new password does not violate defined policies
         passwordPolicyService.verifyPassword(username, newPassword);
diff --git a/extensions/guacamole-auth-quickconnect/src/main/java/org/apache/guacamole/auth/quickconnect/QuickConnectException.java b/extensions/guacamole-auth-quickconnect/src/main/java/org/apache/guacamole/auth/quickconnect/QuickConnectException.java
deleted file mode 100644
index 6e4d5b9..0000000
--- a/extensions/guacamole-auth-quickconnect/src/main/java/org/apache/guacamole/auth/quickconnect/QuickConnectException.java
+++ /dev/null
@@ -1,86 +0,0 @@
-/*
- * 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.auth.quickconnect;
-
-import org.apache.guacamole.GuacamoleClientException;
-import org.apache.guacamole.language.Translatable;
-import org.apache.guacamole.language.TranslatableMessage;
-
-/**
- * An exception that is thrown by this extension when an error occurs
- * attempting to create and establish a connection with a user-provided
- * URI.
- */
-public class QuickConnectException extends GuacamoleClientException
-        implements Translatable {
-    
-    /**
-     * A message that can be passed through the translation service
-     * to provide information about the error that occurred.
-     */
-    private final TranslatableMessage translatableMessage;
-    
-    /**
-     * Create a QuickConnectException with the given message and translationKey.
-     * The message will not be passed through the translation system; the
-     * translationKey will be passed through the translation system.  Both should
-     * describe the error.
-     * 
-     * @param message
-     *     A string describing the error that occurred when trying to create
-     *     or establish the connection.  This will not be passed through the
-     *     translation system.
-     * 
-     * @param translationKey
-     *     A key known to the translation system describing the error that 
-     *     occurred when trying to create or establish the connection.
-     *     This will be passed through the translation system to provide 
-     *     a localized version of the message.
-     */
-    public QuickConnectException(String message, String translationKey) {
-        super(message);
-        this.translatableMessage = new TranslatableMessage(translationKey);
-    }
-    
-    /**
-     * Create a new QuickConnectException given the human-readable message,
-     * which will not be passed through the translation system, and the
-     * translatableMessage, which will be passed through the translation system.
-     * Both parameters should describe the error preventing the connection
-     * from being created or established.
-     * 
-     * @param message
-     *     The human-readable message describing the error, which will not
-     *     be passed through the translation system.
-     * 
-     * @param translatableMessage
-     *     The human-readable message describing the error, which will be
-     *     passed through the translation system.
-     */
-    public QuickConnectException(String message, TranslatableMessage translatableMessage) {
-        super(message);
-        this.translatableMessage = translatableMessage;
-    }
-    
-    @Override
-    public TranslatableMessage getTranslatableMessage() {
-        return translatableMessage;
-    }
-    
-}
diff --git a/extensions/guacamole-auth-quickconnect/src/main/java/org/apache/guacamole/auth/quickconnect/utility/QCParser.java b/extensions/guacamole-auth-quickconnect/src/main/java/org/apache/guacamole/auth/quickconnect/utility/QCParser.java
index 3e2e5e5..e5fc8ec 100644
--- a/extensions/guacamole-auth-quickconnect/src/main/java/org/apache/guacamole/auth/quickconnect/utility/QCParser.java
+++ b/extensions/guacamole-auth-quickconnect/src/main/java/org/apache/guacamole/auth/quickconnect/utility/QCParser.java
@@ -32,7 +32,7 @@
 import java.util.regex.Pattern;
 import org.apache.guacamole.GuacamoleServerException;
 import org.apache.guacamole.GuacamoleException;
-import org.apache.guacamole.auth.quickconnect.QuickConnectException;
+import org.apache.guacamole.language.TranslatableGuacamoleClientException;
 import org.apache.guacamole.protocol.GuacamoleConfiguration;
 
 /**
@@ -78,11 +78,11 @@
         try {
             qcUri = new URI(uri);
             if (!qcUri.isAbsolute())
-                throw new QuickConnectException("URI must be absolute.",
+                throw new TranslatableGuacamoleClientException("URI must be absolute.",
                         "QUICKCONNECT.ERROR_NOT_ABSOLUTE_URI");
         }
         catch (URISyntaxException e) {
-            throw new QuickConnectException("Invalid URI Syntax",
+            throw new TranslatableGuacamoleClientException("Invalid URI Syntax",
                     "QUICKCONNECT.ERROR_INVALID_URI");
         }
 
@@ -100,7 +100,7 @@
         if (protocol != null && !protocol.isEmpty())
             qcConfig.setProtocol(protocol);
         else
-            throw new QuickConnectException("No protocol specified.",
+            throw new TranslatableGuacamoleClientException("No protocol specified.",
                     "QUICKCONNECT.ERROR_NO_PROTOCOL");
 
         // Check for provided port number
@@ -111,7 +111,7 @@
         if (host != null && !host.isEmpty())
             qcConfig.setParameter("hostname", host);
         else
-            throw new QuickConnectException("No host specified.",
+            throw new TranslatableGuacamoleClientException("No host specified.",
                     "QUICKCONNECT.ERROR_NO_HOST");
 
         // Look for extra query parameters and parse them out.
diff --git a/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/user/UserVerificationService.java b/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/user/UserVerificationService.java
index 2414ee8..a73d08f 100644
--- a/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/user/UserVerificationService.java
+++ b/extensions/guacamole-auth-totp/src/main/java/org/apache/guacamole/auth/totp/user/UserVerificationService.java
@@ -27,19 +27,19 @@
 import java.util.HashMap;
 import java.util.Map;
 import javax.servlet.http.HttpServletRequest;
-import org.apache.guacamole.GuacamoleClientException;
 import org.apache.guacamole.GuacamoleException;
 import org.apache.guacamole.GuacamoleSecurityException;
 import org.apache.guacamole.GuacamoleUnsupportedException;
 import org.apache.guacamole.auth.totp.conf.ConfigurationService;
 import org.apache.guacamole.auth.totp.form.AuthenticationCodeField;
 import org.apache.guacamole.form.Field;
+import org.apache.guacamole.language.TranslatableGuacamoleClientException;
+import org.apache.guacamole.language.TranslatableGuacamoleInsufficientCredentialsException;
 import org.apache.guacamole.net.auth.AuthenticatedUser;
 import org.apache.guacamole.net.auth.Credentials;
 import org.apache.guacamole.net.auth.User;
 import org.apache.guacamole.net.auth.UserContext;
 import org.apache.guacamole.net.auth.credentials.CredentialsInfo;
-import org.apache.guacamole.net.auth.credentials.GuacamoleInsufficientCredentialsException;
 import org.apache.guacamole.totp.TOTPGenerator;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -249,15 +249,18 @@
             // If the user hasn't completed enrollment, request that they do
             if (!key.isConfirmed()) {
                 field.exposeKey(key);
-                throw new GuacamoleInsufficientCredentialsException(
+                throw new TranslatableGuacamoleInsufficientCredentialsException(
+                        "TOTP enrollment must be completed before "
+                        + "authentication can continue",
                         "TOTP.INFO_ENROLL_REQUIRED", new CredentialsInfo(
                             Collections.<Field>singletonList(field)
                         ));
             }
 
             // Otherwise simply request the user's authentication code
-            throw new GuacamoleInsufficientCredentialsException(
-                    "TOTP.INFO_CODE_REQUIRED", new CredentialsInfo(
+            throw new TranslatableGuacamoleInsufficientCredentialsException(
+                    "A TOTP authentication code is required before login can "
+                    + "continue", "TOTP.INFO_CODE_REQUIRED", new CredentialsInfo(
                         Collections.<Field>singletonList(field)
                     ));
 
@@ -291,7 +294,8 @@
         }
 
         // Provided code is not valid
-        throw new GuacamoleClientException("TOTP.INFO_VERIFICATION_FAILED");
+        throw new TranslatableGuacamoleClientException("Provided TOTP code "
+                + "is not valid.", "TOTP.INFO_VERIFICATION_FAILED");
 
     }
 
diff --git a/guacamole-docker/bin/build-guacamole.sh b/guacamole-docker/bin/build-guacamole.sh
index 88087e5..1b0b0ec 100755
--- a/guacamole-docker/bin/build-guacamole.sh
+++ b/guacamole-docker/bin/build-guacamole.sh
@@ -127,6 +127,7 @@
     cp extensions/guacamole-auth-radius/target/guacamole-auth-radius*.jar "$DESTINATION/radius"
 fi
 
+#
 # Copy OPENID auth extension and schema modifications
 #
 
@@ -136,6 +137,15 @@
 fi
 
 #
+# Copy TOTP auth extension if it was built
+#
+
+if [ -f extensions/guacamole-auth-totp/target/guacamole-auth-totp*.jar ]; then
+    mkdir -p "$DESTINATION/totp"
+    cp extensions/guacamole-auth-totp/target/guacamole-auth-totp*.jar "$DESTINATION/totp"
+fi
+
+#
 # Copy Duo auth extension if it was built
 #
 
diff --git a/guacamole-docker/bin/start.sh b/guacamole-docker/bin/start.sh
index f5367b4..2763096 100755
--- a/guacamole-docker/bin/start.sh
+++ b/guacamole-docker/bin/start.sh
@@ -369,6 +369,7 @@
     set_optional_property "ldap-encryption-method"  "$LDAP_ENCRYPTION_METHOD"
     set_optional_property "ldap-max-search-results" "$LDAP_MAX_SEARCH_RESULTS"
     set_optional_property "ldap-search-bind-dn"     "$LDAP_SEARCH_BIND_DN"
+    set_optional_property "ldap-user-attributes"    "$LDAP_USER_ATTRIBUTES"
 
     set_optional_property           \
         "ldap-search-bind-password" \
@@ -537,6 +538,21 @@
 }
 
 ##
+## Adds properties to guacamole.properties which configure the TOTP two-factor
+## authentication mechanism.
+##
+associate_totp() {
+    # Update config file
+    set_optional_property "totp-issuer"    "$TOTP_ISSUER"
+    set_optional_property "totp-digits"    "$TOTP_DIGITS"
+    set_optional_property "totp-period"    "$TOTP_PERIOD"
+    set_optional_property "totp-mode"      "$TOTP_MODE"
+
+    # Add required .jar files to GUACAMOLE_EXT
+    ln -s /opt/guacamole/totp/guacamole-auth-*.jar   "$GUACAMOLE_EXT"
+}
+
+##
 ## Adds properties to guacamole.properties which configure the Duo two-factor
 ## authentication service. Checks to see if all variables are defined and makes sure
 ## DUO_APPLICATION_KEY is >= 40 characters.
@@ -707,6 +723,11 @@
     exit 1;
 fi
 
+# Use TOTP if specified.
+if [ "$TOTP_ENABLED" = "true" ]; then
+    associate_totp
+fi
+
 # Use Duo if specified.
 if [ -n "$DUO_API_HOSTNAME" ]; then
     associate_duo
diff --git a/guacamole-ext/src/main/java/org/apache/guacamole/language/TranslatableGuacamoleClientBadTypeException.java b/guacamole-ext/src/main/java/org/apache/guacamole/language/TranslatableGuacamoleClientBadTypeException.java
new file mode 100644
index 0000000..e48b1cf
--- /dev/null
+++ b/guacamole-ext/src/main/java/org/apache/guacamole/language/TranslatableGuacamoleClientBadTypeException.java
@@ -0,0 +1,122 @@
+/*
+ * 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.language;
+
+import org.apache.guacamole.GuacamoleClientBadTypeException;
+
+/**
+ * A {@link GuacamoleClientBadTypeException} whose associated message is
+ * translatable and can be passed through an arbitrary translation service,
+ * producing a human-readable message in the user's native language.
+ */
+public class TranslatableGuacamoleClientBadTypeException extends GuacamoleClientBadTypeException implements Translatable {
+
+    /**
+     * A translatable, human-readable description of the exception that
+     * occurred.
+     */
+    private final TranslatableMessage translatableMessage;
+
+    /**
+     * Creates a new TranslatableGuacamoleClientBadTypeException with the given
+     * message and cause. The message must be provided in both non-translatable
+     * (readable as-written) and translatable forms.
+     *
+     * @param message
+     *     A human-readable description of the exception that occurred. This
+     *     message should be readable on its own and as-written, without
+     *     requiring a translation service.
+     *
+     * @param translatableMessage
+     *     A translatable, human-readable description of the exception that
+     *     occurred.
+     *
+     * @param cause
+     *     The cause of this exception.
+     */
+    public TranslatableGuacamoleClientBadTypeException(String message, TranslatableMessage translatableMessage, Throwable cause) {
+        super(message, cause);
+        this.translatableMessage = translatableMessage;
+    }
+
+    /**
+     * Creates a new TranslatableGuacamoleClientBadTypeException with the given
+     * message. The message must be provided in both non-translatable (readable
+     * as-written) and translatable forms.
+     *
+     * @param message
+     *     A human-readable description of the exception that occurred. This
+     *     message should be readable on its own and as-written, without
+     *     requiring a translation service.
+     *
+     * @param translatableMessage
+     *     A translatable, human-readable description of the exception that
+     *     occurred.
+     */
+    public TranslatableGuacamoleClientBadTypeException(String message, TranslatableMessage translatableMessage) {
+        super(message);
+        this.translatableMessage = translatableMessage;
+    }
+
+    /**
+     * Creates a new TranslatableGuacamoleClientBadTypeException with the given
+     * message and cause. The message must be provided in both non-translatable
+     * (readable as-written) and translatable forms.
+     *
+     * @param message
+     *     A human-readable description of the exception that occurred. This
+     *     message should be readable on its own and as-written, without
+     *     requiring a translation service.
+     *
+     * @param key
+     *     The arbitrary key which can be used to look up the message to be
+     *     displayed in the user's native language.
+     *
+     * @param cause
+     *     The cause of this exception.
+     */
+    public TranslatableGuacamoleClientBadTypeException(String message, String key, Throwable cause) {
+        this(message, new TranslatableMessage(key), cause);
+    }
+
+    /**
+     * Creates a new TranslatableGuacamoleClientBadTypeException with the given
+     * message. The message must be provided in both non-translatable (readable
+     * as-written) and translatable forms.
+     *
+     * @param message
+     *     A human-readable description of the exception that occurred. This
+     *     message should be readable on its own and as-written, without
+     *     requiring a translation service.
+     *
+     * @param key
+     *     The arbitrary key which can be used to look up the message to be
+     *     displayed in the user's native language.
+     */
+    public TranslatableGuacamoleClientBadTypeException(String message, String key) {
+        this(message, new TranslatableMessage(key));
+    }
+
+    @Override
+    public TranslatableMessage getTranslatableMessage() {
+        return translatableMessage;
+    }
+
+}
diff --git a/guacamole-ext/src/main/java/org/apache/guacamole/language/TranslatableGuacamoleClientException.java b/guacamole-ext/src/main/java/org/apache/guacamole/language/TranslatableGuacamoleClientException.java
new file mode 100644
index 0000000..59818ed
--- /dev/null
+++ b/guacamole-ext/src/main/java/org/apache/guacamole/language/TranslatableGuacamoleClientException.java
@@ -0,0 +1,122 @@
+/*
+ * 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.language;
+
+import org.apache.guacamole.GuacamoleClientException;
+
+/**
+ * A {@link GuacamoleClientException} whose associated message is translatable
+ * and can be passed through an arbitrary translation service, producing a
+ * human-readable message in the user's native language.
+ */
+public class TranslatableGuacamoleClientException extends GuacamoleClientException implements Translatable {
+
+    /**
+     * A translatable, human-readable description of the exception that
+     * occurred.
+     */
+    private final TranslatableMessage translatableMessage;
+
+    /**
+     * Creates a new TranslatableGuacamoleClientException with the given
+     * message and cause. The message must be provided in both non-translatable
+     * (readable as-written) and translatable forms.
+     *
+     * @param message
+     *     A human-readable description of the exception that occurred. This
+     *     message should be readable on its own and as-written, without
+     *     requiring a translation service.
+     *
+     * @param translatableMessage
+     *     A translatable, human-readable description of the exception that
+     *     occurred.
+     *
+     * @param cause
+     *     The cause of this exception.
+     */
+    public TranslatableGuacamoleClientException(String message, TranslatableMessage translatableMessage, Throwable cause) {
+        super(message, cause);
+        this.translatableMessage = translatableMessage;
+    }
+
+    /**
+     * Creates a new TranslatableGuacamoleClientException with the given
+     * message. The message must be provided in both non-translatable (readable
+     * as-written) and translatable forms.
+     *
+     * @param message
+     *     A human-readable description of the exception that occurred. This
+     *     message should be readable on its own and as-written, without
+     *     requiring a translation service.
+     *
+     * @param translatableMessage
+     *     A translatable, human-readable description of the exception that
+     *     occurred.
+     */
+    public TranslatableGuacamoleClientException(String message, TranslatableMessage translatableMessage) {
+        super(message);
+        this.translatableMessage = translatableMessage;
+    }
+
+    /**
+     * Creates a new TranslatableGuacamoleClientException with the given
+     * message and cause. The message must be provided in both non-translatable
+     * (readable as-written) and translatable forms.
+     *
+     * @param message
+     *     A human-readable description of the exception that occurred. This
+     *     message should be readable on its own and as-written, without
+     *     requiring a translation service.
+     *
+     * @param key
+     *     The arbitrary key which can be used to look up the message to be
+     *     displayed in the user's native language.
+     *
+     * @param cause
+     *     The cause of this exception.
+     */
+    public TranslatableGuacamoleClientException(String message, String key, Throwable cause) {
+        this(message, new TranslatableMessage(key), cause);
+    }
+
+    /**
+     * Creates a new TranslatableGuacamoleClientException with the given
+     * message. The message must be provided in both non-translatable (readable
+     * as-written) and translatable forms.
+     *
+     * @param message
+     *     A human-readable description of the exception that occurred. This
+     *     message should be readable on its own and as-written, without
+     *     requiring a translation service.
+     *
+     * @param key
+     *     The arbitrary key which can be used to look up the message to be
+     *     displayed in the user's native language.
+     */
+    public TranslatableGuacamoleClientException(String message, String key) {
+        this(message, new TranslatableMessage(key));
+    }
+
+    @Override
+    public TranslatableMessage getTranslatableMessage() {
+        return translatableMessage;
+    }
+
+}
diff --git a/guacamole-ext/src/main/java/org/apache/guacamole/language/TranslatableGuacamoleClientOverrunException.java b/guacamole-ext/src/main/java/org/apache/guacamole/language/TranslatableGuacamoleClientOverrunException.java
new file mode 100644
index 0000000..3ad94c8
--- /dev/null
+++ b/guacamole-ext/src/main/java/org/apache/guacamole/language/TranslatableGuacamoleClientOverrunException.java
@@ -0,0 +1,122 @@
+/*
+ * 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.language;
+
+import org.apache.guacamole.GuacamoleClientOverrunException;
+
+/**
+ * A {@link GuacamoleClientOverrunException} whose associated message is
+ * translatable and can be passed through an arbitrary translation service,
+ * producing a human-readable message in the user's native language.
+ */
+public class TranslatableGuacamoleClientOverrunException extends GuacamoleClientOverrunException implements Translatable {
+
+    /**
+     * A translatable, human-readable description of the exception that
+     * occurred.
+     */
+    private final TranslatableMessage translatableMessage;
+
+    /**
+     * Creates a new TranslatableGuacamoleClientOverrunException with the given
+     * message and cause. The message must be provided in both non-translatable
+     * (readable as-written) and translatable forms.
+     *
+     * @param message
+     *     A human-readable description of the exception that occurred. This
+     *     message should be readable on its own and as-written, without
+     *     requiring a translation service.
+     *
+     * @param translatableMessage
+     *     A translatable, human-readable description of the exception that
+     *     occurred.
+     *
+     * @param cause
+     *     The cause of this exception.
+     */
+    public TranslatableGuacamoleClientOverrunException(String message, TranslatableMessage translatableMessage, Throwable cause) {
+        super(message, cause);
+        this.translatableMessage = translatableMessage;
+    }
+
+    /**
+     * Creates a new TranslatableGuacamoleClientOverrunException with the given
+     * message. The message must be provided in both non-translatable (readable
+     * as-written) and translatable forms.
+     *
+     * @param message
+     *     A human-readable description of the exception that occurred. This
+     *     message should be readable on its own and as-written, without
+     *     requiring a translation service.
+     *
+     * @param translatableMessage
+     *     A translatable, human-readable description of the exception that
+     *     occurred.
+     */
+    public TranslatableGuacamoleClientOverrunException(String message, TranslatableMessage translatableMessage) {
+        super(message);
+        this.translatableMessage = translatableMessage;
+    }
+
+    /**
+     * Creates a new TranslatableGuacamoleClientOverrunException with the given
+     * message and cause. The message must be provided in both non-translatable
+     * (readable as-written) and translatable forms.
+     *
+     * @param message
+     *     A human-readable description of the exception that occurred. This
+     *     message should be readable on its own and as-written, without
+     *     requiring a translation service.
+     *
+     * @param key
+     *     The arbitrary key which can be used to look up the message to be
+     *     displayed in the user's native language.
+     *
+     * @param cause
+     *     The cause of this exception.
+     */
+    public TranslatableGuacamoleClientOverrunException(String message, String key, Throwable cause) {
+        this(message, new TranslatableMessage(key), cause);
+    }
+
+    /**
+     * Creates a new TranslatableGuacamoleClientOverrunException with the given
+     * message. The message must be provided in both non-translatable (readable
+     * as-written) and translatable forms.
+     *
+     * @param message
+     *     A human-readable description of the exception that occurred. This
+     *     message should be readable on its own and as-written, without
+     *     requiring a translation service.
+     *
+     * @param key
+     *     The arbitrary key which can be used to look up the message to be
+     *     displayed in the user's native language.
+     */
+    public TranslatableGuacamoleClientOverrunException(String message, String key) {
+        this(message, new TranslatableMessage(key));
+    }
+
+    @Override
+    public TranslatableMessage getTranslatableMessage() {
+        return translatableMessage;
+    }
+
+}
diff --git a/guacamole-ext/src/main/java/org/apache/guacamole/language/TranslatableGuacamoleClientTimeoutException.java b/guacamole-ext/src/main/java/org/apache/guacamole/language/TranslatableGuacamoleClientTimeoutException.java
new file mode 100644
index 0000000..6b9ea7e
--- /dev/null
+++ b/guacamole-ext/src/main/java/org/apache/guacamole/language/TranslatableGuacamoleClientTimeoutException.java
@@ -0,0 +1,122 @@
+/*
+ * 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.language;
+
+import org.apache.guacamole.GuacamoleClientTimeoutException;
+
+/**
+ * A {@link GuacamoleClientTimeoutException} whose associated message is
+ * translatable and can be passed through an arbitrary translation service,
+ * producing a human-readable message in the user's native language.
+ */
+public class TranslatableGuacamoleClientTimeoutException extends GuacamoleClientTimeoutException implements Translatable {
+
+    /**
+     * A translatable, human-readable description of the exception that
+     * occurred.
+     */
+    private final TranslatableMessage translatableMessage;
+
+    /**
+     * Creates a new TranslatableGuacamoleClientTimeoutException with the given
+     * message and cause. The message must be provided in both non-translatable
+     * (readable as-written) and translatable forms.
+     *
+     * @param message
+     *     A human-readable description of the exception that occurred. This
+     *     message should be readable on its own and as-written, without
+     *     requiring a translation service.
+     *
+     * @param translatableMessage
+     *     A translatable, human-readable description of the exception that
+     *     occurred.
+     *
+     * @param cause
+     *     The cause of this exception.
+     */
+    public TranslatableGuacamoleClientTimeoutException(String message, TranslatableMessage translatableMessage, Throwable cause) {
+        super(message, cause);
+        this.translatableMessage = translatableMessage;
+    }
+
+    /**
+     * Creates a new TranslatableGuacamoleClientTimeoutException with the given
+     * message. The message must be provided in both non-translatable (readable
+     * as-written) and translatable forms.
+     *
+     * @param message
+     *     A human-readable description of the exception that occurred. This
+     *     message should be readable on its own and as-written, without
+     *     requiring a translation service.
+     *
+     * @param translatableMessage
+     *     A translatable, human-readable description of the exception that
+     *     occurred.
+     */
+    public TranslatableGuacamoleClientTimeoutException(String message, TranslatableMessage translatableMessage) {
+        super(message);
+        this.translatableMessage = translatableMessage;
+    }
+
+    /**
+     * Creates a new TranslatableGuacamoleClientTimeoutException with the given
+     * message and cause. The message must be provided in both non-translatable
+     * (readable as-written) and translatable forms.
+     *
+     * @param message
+     *     A human-readable description of the exception that occurred. This
+     *     message should be readable on its own and as-written, without
+     *     requiring a translation service.
+     *
+     * @param key
+     *     The arbitrary key which can be used to look up the message to be
+     *     displayed in the user's native language.
+     *
+     * @param cause
+     *     The cause of this exception.
+     */
+    public TranslatableGuacamoleClientTimeoutException(String message, String key, Throwable cause) {
+        this(message, new TranslatableMessage(key), cause);
+    }
+
+    /**
+     * Creates a new TranslatableGuacamoleClientTimeoutException with the given
+     * message. The message must be provided in both non-translatable (readable
+     * as-written) and translatable forms.
+     *
+     * @param message
+     *     A human-readable description of the exception that occurred. This
+     *     message should be readable on its own and as-written, without
+     *     requiring a translation service.
+     *
+     * @param key
+     *     The arbitrary key which can be used to look up the message to be
+     *     displayed in the user's native language.
+     */
+    public TranslatableGuacamoleClientTimeoutException(String message, String key) {
+        this(message, new TranslatableMessage(key));
+    }
+
+    @Override
+    public TranslatableMessage getTranslatableMessage() {
+        return translatableMessage;
+    }
+
+}
diff --git a/guacamole-ext/src/main/java/org/apache/guacamole/language/TranslatableGuacamoleClientTooManyException.java b/guacamole-ext/src/main/java/org/apache/guacamole/language/TranslatableGuacamoleClientTooManyException.java
new file mode 100644
index 0000000..d1ba1ca
--- /dev/null
+++ b/guacamole-ext/src/main/java/org/apache/guacamole/language/TranslatableGuacamoleClientTooManyException.java
@@ -0,0 +1,122 @@
+/*
+ * 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.language;
+
+import org.apache.guacamole.GuacamoleClientTooManyException;
+
+/**
+ * A {@link GuacamoleClientTooManyException} whose associated message is
+ * translatable and can be passed through an arbitrary translation service,
+ * producing a human-readable message in the user's native language.
+ */
+public class TranslatableGuacamoleClientTooManyException extends GuacamoleClientTooManyException implements Translatable {
+
+    /**
+     * A translatable, human-readable description of the exception that
+     * occurred.
+     */
+    private final TranslatableMessage translatableMessage;
+
+    /**
+     * Creates a new TranslatableGuacamoleClientTooManyException with the given
+     * message and cause. The message must be provided in both non-translatable
+     * (readable as-written) and translatable forms.
+     *
+     * @param message
+     *     A human-readable description of the exception that occurred. This
+     *     message should be readable on its own and as-written, without
+     *     requiring a translation service.
+     *
+     * @param translatableMessage
+     *     A translatable, human-readable description of the exception that
+     *     occurred.
+     *
+     * @param cause
+     *     The cause of this exception.
+     */
+    public TranslatableGuacamoleClientTooManyException(String message, TranslatableMessage translatableMessage, Throwable cause) {
+        super(message, cause);
+        this.translatableMessage = translatableMessage;
+    }
+
+    /**
+     * Creates a new TranslatableGuacamoleClientTooManyException with the given
+     * message. The message must be provided in both non-translatable (readable
+     * as-written) and translatable forms.
+     *
+     * @param message
+     *     A human-readable description of the exception that occurred. This
+     *     message should be readable on its own and as-written, without
+     *     requiring a translation service.
+     *
+     * @param translatableMessage
+     *     A translatable, human-readable description of the exception that
+     *     occurred.
+     */
+    public TranslatableGuacamoleClientTooManyException(String message, TranslatableMessage translatableMessage) {
+        super(message);
+        this.translatableMessage = translatableMessage;
+    }
+
+    /**
+     * Creates a new TranslatableGuacamoleClientTooManyException with the given
+     * message and cause. The message must be provided in both non-translatable
+     * (readable as-written) and translatable forms.
+     *
+     * @param message
+     *     A human-readable description of the exception that occurred. This
+     *     message should be readable on its own and as-written, without
+     *     requiring a translation service.
+     *
+     * @param key
+     *     The arbitrary key which can be used to look up the message to be
+     *     displayed in the user's native language.
+     *
+     * @param cause
+     *     The cause of this exception.
+     */
+    public TranslatableGuacamoleClientTooManyException(String message, String key, Throwable cause) {
+        this(message, new TranslatableMessage(key), cause);
+    }
+
+    /**
+     * Creates a new TranslatableGuacamoleClientTooManyException with the given
+     * message. The message must be provided in both non-translatable (readable
+     * as-written) and translatable forms.
+     *
+     * @param message
+     *     A human-readable description of the exception that occurred. This
+     *     message should be readable on its own and as-written, without
+     *     requiring a translation service.
+     *
+     * @param key
+     *     The arbitrary key which can be used to look up the message to be
+     *     displayed in the user's native language.
+     */
+    public TranslatableGuacamoleClientTooManyException(String message, String key) {
+        this(message, new TranslatableMessage(key));
+    }
+
+    @Override
+    public TranslatableMessage getTranslatableMessage() {
+        return translatableMessage;
+    }
+
+}
diff --git a/guacamole-ext/src/main/java/org/apache/guacamole/language/TranslatableGuacamoleConnectionClosedException.java b/guacamole-ext/src/main/java/org/apache/guacamole/language/TranslatableGuacamoleConnectionClosedException.java
new file mode 100644
index 0000000..9b615a5
--- /dev/null
+++ b/guacamole-ext/src/main/java/org/apache/guacamole/language/TranslatableGuacamoleConnectionClosedException.java
@@ -0,0 +1,122 @@
+/*
+ * 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.language;
+
+import org.apache.guacamole.GuacamoleConnectionClosedException;
+
+/**
+ * A {@link GuacamoleConnectionClosedException} whose associated message is
+ * translatable and can be passed through an arbitrary translation service,
+ * producing a human-readable message in the user's native language.
+ */
+public class TranslatableGuacamoleConnectionClosedException extends GuacamoleConnectionClosedException implements Translatable {
+
+    /**
+     * A translatable, human-readable description of the exception that
+     * occurred.
+     */
+    private final TranslatableMessage translatableMessage;
+
+    /**
+     * Creates a new TranslatableGuacamoleConnectionClosedException with the
+     * given message and cause. The message must be provided in both
+     * non-translatable (readable as-written) and translatable forms.
+     *
+     * @param message
+     *     A human-readable description of the exception that occurred. This
+     *     message should be readable on its own and as-written, without
+     *     requiring a translation service.
+     *
+     * @param translatableMessage
+     *     A translatable, human-readable description of the exception that
+     *     occurred.
+     *
+     * @param cause
+     *     The cause of this exception.
+     */
+    public TranslatableGuacamoleConnectionClosedException(String message, TranslatableMessage translatableMessage, Throwable cause) {
+        super(message, cause);
+        this.translatableMessage = translatableMessage;
+    }
+
+    /**
+     * Creates a new TranslatableGuacamoleConnectionClosedException with the
+     * given message. The message must be provided in both non-translatable
+     * (readable as-written) and translatable forms.
+     *
+     * @param message
+     *     A human-readable description of the exception that occurred. This
+     *     message should be readable on its own and as-written, without
+     *     requiring a translation service.
+     *
+     * @param translatableMessage
+     *     A translatable, human-readable description of the exception that
+     *     occurred.
+     */
+    public TranslatableGuacamoleConnectionClosedException(String message, TranslatableMessage translatableMessage) {
+        super(message);
+        this.translatableMessage = translatableMessage;
+    }
+
+    /**
+     * Creates a new TranslatableGuacamoleConnectionClosedException with the
+     * given message and cause. The message must be provided in both
+     * non-translatable (readable as-written) and translatable forms.
+     *
+     * @param message
+     *     A human-readable description of the exception that occurred. This
+     *     message should be readable on its own and as-written, without
+     *     requiring a translation service.
+     *
+     * @param key
+     *     The arbitrary key which can be used to look up the message to be
+     *     displayed in the user's native language.
+     *
+     * @param cause
+     *     The cause of this exception.
+     */
+    public TranslatableGuacamoleConnectionClosedException(String message, String key, Throwable cause) {
+        this(message, new TranslatableMessage(key), cause);
+    }
+
+    /**
+     * Creates a new TranslatableGuacamoleConnectionClosedException with the
+     * given message. The message must be provided in both non-translatable
+     * (readable as-written) and translatable forms.
+     *
+     * @param message
+     *     A human-readable description of the exception that occurred. This
+     *     message should be readable on its own and as-written, without
+     *     requiring a translation service.
+     *
+     * @param key
+     *     The arbitrary key which can be used to look up the message to be
+     *     displayed in the user's native language.
+     */
+    public TranslatableGuacamoleConnectionClosedException(String message, String key) {
+        this(message, new TranslatableMessage(key));
+    }
+
+    @Override
+    public TranslatableMessage getTranslatableMessage() {
+        return translatableMessage;
+    }
+
+}
diff --git a/guacamole-ext/src/main/java/org/apache/guacamole/language/TranslatableGuacamoleCredentialsException.java b/guacamole-ext/src/main/java/org/apache/guacamole/language/TranslatableGuacamoleCredentialsException.java
new file mode 100644
index 0000000..86c04b8
--- /dev/null
+++ b/guacamole-ext/src/main/java/org/apache/guacamole/language/TranslatableGuacamoleCredentialsException.java
@@ -0,0 +1,144 @@
+/*
+ * 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.language;
+
+import org.apache.guacamole.net.auth.credentials.CredentialsInfo;
+import org.apache.guacamole.net.auth.credentials.GuacamoleCredentialsException;
+
+/**
+ * A {@link GuacamoleCredentialsException} whose associated message is
+ * translatable and can be passed through an arbitrary translation service,
+ * producing a human-readable message in the user's native language.
+ */
+public class TranslatableGuacamoleCredentialsException
+        extends GuacamoleCredentialsException implements Translatable {
+
+    /**
+     * A translatable, human-readable description of the exception that
+     * occurred.
+     */
+    private final TranslatableMessage translatableMessage;
+
+    /**
+     * Creates a new TranslatableGuacamoleCredentialsException with the given
+     * message, cause, and associated credential information. The message must
+     * be provided in both non-translatable (readable as-written) and
+     * translatable forms.
+     *
+     * @param message
+     *     A human-readable description of the exception that occurred. This
+     *     message should be readable on its own and as-written, without
+     *     requiring a translation service.
+     *
+     * @param translatableMessage
+     *     A translatable, human-readable description of the exception that
+     *     occurred.
+     *
+     * @param cause
+     *     The cause of this exception.
+     *
+     * @param credentialsInfo
+     *     Information describing the form of valid credentials.
+     */
+    public TranslatableGuacamoleCredentialsException(String message,
+            TranslatableMessage translatableMessage, Throwable cause, CredentialsInfo credentialsInfo) {
+        super(message, cause, credentialsInfo);
+        this.translatableMessage = translatableMessage;
+    }
+
+    /**
+     * Creates a new TranslatableGuacamoleCredentialsException with the given
+     * message, and associated credential information. The message must be
+     * provided in both non-translatable (readable as-written) and translatable
+     * forms.
+     *
+     * @param message
+     *     A human-readable description of the exception that occurred. This
+     *     message should be readable on its own and as-written, without
+     *     requiring a translation service.
+     *
+     * @param translatableMessage
+     *     A translatable, human-readable description of the exception that
+     *     occurred.
+     *
+     * @param credentialsInfo
+     *     Information describing the form of valid credentials.
+     */
+    public TranslatableGuacamoleCredentialsException(String message,
+            TranslatableMessage translatableMessage, CredentialsInfo credentialsInfo) {
+        super(message, credentialsInfo);
+        this.translatableMessage = translatableMessage;
+    }
+
+    /**
+     * Creates a new TranslatableGuacamoleCredentialsException with the given
+     * message, cause, and associated credential information. The message must
+     * be provided in both non-translatable (readable as-written) and
+     * translatable forms.
+     *
+     * @param message
+     *     A human-readable description of the exception that occurred. This
+     *     message should be readable on its own and as-written, without
+     *     requiring a translation service.
+     *
+     * @param key
+     *     The arbitrary key which can be used to look up the message to be
+     *     displayed in the user's native language.
+     *
+     * @param cause
+     *     The cause of this exception.
+     *
+     * @param credentialsInfo
+     *     Information describing the form of valid credentials.
+     */
+    public TranslatableGuacamoleCredentialsException(String message,
+            String key, Throwable cause, CredentialsInfo credentialsInfo) {
+        this(message, new TranslatableMessage(key), cause, credentialsInfo);
+    }
+
+    /**
+     * Creates a new TranslatableGuacamoleCredentialsException with the given
+     * message, and associated credential information. The message must be
+     * provided in both non-translatable (readable as-written) and translatable
+     * forms.
+     *
+     * @param message
+     *     A human-readable description of the exception that occurred. This
+     *     message should be readable on its own and as-written, without
+     *     requiring a translation service.
+     *
+     * @param key
+     *     The arbitrary key which can be used to look up the message to be
+     *     displayed in the user's native language.
+     *
+     * @param credentialsInfo
+     *     Information describing the form of valid credentials.
+     */
+    public TranslatableGuacamoleCredentialsException(String message,
+            String key, CredentialsInfo credentialsInfo) {
+        this(message, new TranslatableMessage(key), credentialsInfo);
+    }
+
+    @Override
+    public TranslatableMessage getTranslatableMessage() {
+        return translatableMessage;
+    }
+
+}
diff --git a/guacamole-ext/src/main/java/org/apache/guacamole/language/TranslatableGuacamoleInsufficientCredentialsException.java b/guacamole-ext/src/main/java/org/apache/guacamole/language/TranslatableGuacamoleInsufficientCredentialsException.java
new file mode 100644
index 0000000..5b51d2c
--- /dev/null
+++ b/guacamole-ext/src/main/java/org/apache/guacamole/language/TranslatableGuacamoleInsufficientCredentialsException.java
@@ -0,0 +1,144 @@
+/*
+ * 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.language;
+
+import org.apache.guacamole.net.auth.credentials.CredentialsInfo;
+import org.apache.guacamole.net.auth.credentials.GuacamoleInsufficientCredentialsException;
+
+/**
+ * A {@link GuacamoleInsufficientCredentialsException} whose associated message
+ * is translatable and can be passed through an arbitrary translation service,
+ * producing a human-readable message in the user's native language.
+ */
+public class TranslatableGuacamoleInsufficientCredentialsException
+        extends GuacamoleInsufficientCredentialsException implements Translatable {
+
+    /**
+     * A translatable, human-readable description of the exception that
+     * occurred.
+     */
+    private final TranslatableMessage translatableMessage;
+
+    /**
+     * Creates a new TranslatableGuacamoleInsufficientCredentialsException with
+     * the given message, cause, and associated credential information. The
+     * message must be provided in both non-translatable (readable as-written)
+     * and translatable forms.
+     *
+     * @param message
+     *     A human-readable description of the exception that occurred. This
+     *     message should be readable on its own and as-written, without
+     *     requiring a translation service.
+     *
+     * @param translatableMessage
+     *     A translatable, human-readable description of the exception that
+     *     occurred.
+     *
+     * @param cause
+     *     The cause of this exception.
+     *
+     * @param credentialsInfo
+     *     Information describing the form of valid credentials.
+     */
+    public TranslatableGuacamoleInsufficientCredentialsException(String message,
+            TranslatableMessage translatableMessage, Throwable cause, CredentialsInfo credentialsInfo) {
+        super(message, cause, credentialsInfo);
+        this.translatableMessage = translatableMessage;
+    }
+
+    /**
+     * Creates a new TranslatableGuacamoleInsufficientCredentialsException with
+     * the given message, and associated credential information. The message
+     * must be provided in both non-translatable (readable as-written) and
+     * translatable forms.
+     *
+     * @param message
+     *     A human-readable description of the exception that occurred. This
+     *     message should be readable on its own and as-written, without
+     *     requiring a translation service.
+     *
+     * @param translatableMessage
+     *     A translatable, human-readable description of the exception that
+     *     occurred.
+     *
+     * @param credentialsInfo
+     *     Information describing the form of valid credentials.
+     */
+    public TranslatableGuacamoleInsufficientCredentialsException(String message,
+            TranslatableMessage translatableMessage, CredentialsInfo credentialsInfo) {
+        super(message, credentialsInfo);
+        this.translatableMessage = translatableMessage;
+    }
+
+    /**
+     * Creates a new TranslatableGuacamoleInsufficientCredentialsException with
+     * the given message, cause, and associated credential information. The
+     * message must be provided in both non-translatable (readable as-written)
+     * and translatable forms.
+     *
+     * @param message
+     *     A human-readable description of the exception that occurred. This
+     *     message should be readable on its own and as-written, without
+     *     requiring a translation service.
+     *
+     * @param key
+     *     The arbitrary key which can be used to look up the message to be
+     *     displayed in the user's native language.
+     *
+     * @param cause
+     *     The cause of this exception.
+     *
+     * @param credentialsInfo
+     *     Information describing the form of valid credentials.
+     */
+    public TranslatableGuacamoleInsufficientCredentialsException(String message,
+            String key, Throwable cause, CredentialsInfo credentialsInfo) {
+        this(message, new TranslatableMessage(key), cause, credentialsInfo);
+    }
+
+    /**
+     * Creates a new TranslatableGuacamoleInsufficientCredentialsException with
+     * the given message, and associated credential information. The message
+     * must be provided in both non-translatable (readable as-written) and
+     * translatable forms.
+     *
+     * @param message
+     *     A human-readable description of the exception that occurred. This
+     *     message should be readable on its own and as-written, without
+     *     requiring a translation service.
+     *
+     * @param key
+     *     The arbitrary key which can be used to look up the message to be
+     *     displayed in the user's native language.
+     *
+     * @param credentialsInfo
+     *     Information describing the form of valid credentials.
+     */
+    public TranslatableGuacamoleInsufficientCredentialsException(String message,
+            String key, CredentialsInfo credentialsInfo) {
+        this(message, new TranslatableMessage(key), credentialsInfo);
+    }
+
+    @Override
+    public TranslatableMessage getTranslatableMessage() {
+        return translatableMessage;
+    }
+
+}
diff --git a/guacamole-ext/src/main/java/org/apache/guacamole/language/TranslatableGuacamoleInvalidCredentialsException.java b/guacamole-ext/src/main/java/org/apache/guacamole/language/TranslatableGuacamoleInvalidCredentialsException.java
new file mode 100644
index 0000000..7d878ec
--- /dev/null
+++ b/guacamole-ext/src/main/java/org/apache/guacamole/language/TranslatableGuacamoleInvalidCredentialsException.java
@@ -0,0 +1,144 @@
+/*
+ * 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.language;
+
+import org.apache.guacamole.net.auth.credentials.CredentialsInfo;
+import org.apache.guacamole.net.auth.credentials.GuacamoleInvalidCredentialsException;
+
+/**
+ * A {@link GuacamoleInvalidCredentialsException} whose associated message
+ * is translatable and can be passed through an arbitrary translation service,
+ * producing a human-readable message in the user's native language.
+ */
+public class TranslatableGuacamoleInvalidCredentialsException
+        extends GuacamoleInvalidCredentialsException implements Translatable {
+
+    /**
+     * A translatable, human-readable description of the exception that
+     * occurred.
+     */
+    private final TranslatableMessage translatableMessage;
+
+    /**
+     * Creates a new TranslatableGuacamoleInvalidCredentialsException with
+     * the given message, cause, and associated credential information. The
+     * message must be provided in both non-translatable (readable as-written)
+     * and translatable forms.
+     *
+     * @param message
+     *     A human-readable description of the exception that occurred. This
+     *     message should be readable on its own and as-written, without
+     *     requiring a translation service.
+     *
+     * @param translatableMessage
+     *     A translatable, human-readable description of the exception that
+     *     occurred.
+     *
+     * @param cause
+     *     The cause of this exception.
+     *
+     * @param credentialsInfo
+     *     Information describing the form of valid credentials.
+     */
+    public TranslatableGuacamoleInvalidCredentialsException(String message,
+            TranslatableMessage translatableMessage, Throwable cause, CredentialsInfo credentialsInfo) {
+        super(message, cause, credentialsInfo);
+        this.translatableMessage = translatableMessage;
+    }
+
+    /**
+     * Creates a new TranslatableGuacamoleInvalidCredentialsException with
+     * the given message, and associated credential information. The message
+     * must be provided in both non-translatable (readable as-written) and
+     * translatable forms.
+     *
+     * @param message
+     *     A human-readable description of the exception that occurred. This
+     *     message should be readable on its own and as-written, without
+     *     requiring a translation service.
+     *
+     * @param translatableMessage
+     *     A translatable, human-readable description of the exception that
+     *     occurred.
+     *
+     * @param credentialsInfo
+     *     Information describing the form of valid credentials.
+     */
+    public TranslatableGuacamoleInvalidCredentialsException(String message,
+            TranslatableMessage translatableMessage, CredentialsInfo credentialsInfo) {
+        super(message, credentialsInfo);
+        this.translatableMessage = translatableMessage;
+    }
+
+    /**
+     * Creates a new TranslatableGuacamoleInvalidCredentialsException with
+     * the given message, cause, and associated credential information. The
+     * message must be provided in both non-translatable (readable as-written)
+     * and translatable forms.
+     *
+     * @param message
+     *     A human-readable description of the exception that occurred. This
+     *     message should be readable on its own and as-written, without
+     *     requiring a translation service.
+     *
+     * @param key
+     *     The arbitrary key which can be used to look up the message to be
+     *     displayed in the user's native language.
+     *
+     * @param cause
+     *     The cause of this exception.
+     *
+     * @param credentialsInfo
+     *     Information describing the form of valid credentials.
+     */
+    public TranslatableGuacamoleInvalidCredentialsException(String message,
+            String key, Throwable cause, CredentialsInfo credentialsInfo) {
+        this(message, new TranslatableMessage(key), cause, credentialsInfo);
+    }
+
+    /**
+     * Creates a new TranslatableGuacamoleInvalidCredentialsException with
+     * the given message, and associated credential information. The message
+     * must be provided in both non-translatable (readable as-written) and
+     * translatable forms.
+     *
+     * @param message
+     *     A human-readable description of the exception that occurred. This
+     *     message should be readable on its own and as-written, without
+     *     requiring a translation service.
+     *
+     * @param key
+     *     The arbitrary key which can be used to look up the message to be
+     *     displayed in the user's native language.
+     *
+     * @param credentialsInfo
+     *     Information describing the form of valid credentials.
+     */
+    public TranslatableGuacamoleInvalidCredentialsException(String message,
+            String key, CredentialsInfo credentialsInfo) {
+        this(message, new TranslatableMessage(key), credentialsInfo);
+    }
+
+    @Override
+    public TranslatableMessage getTranslatableMessage() {
+        return translatableMessage;
+    }
+
+}
diff --git a/guacamole-ext/src/main/java/org/apache/guacamole/language/TranslatableGuacamoleResourceClosedException.java b/guacamole-ext/src/main/java/org/apache/guacamole/language/TranslatableGuacamoleResourceClosedException.java
new file mode 100644
index 0000000..b0e5b48
--- /dev/null
+++ b/guacamole-ext/src/main/java/org/apache/guacamole/language/TranslatableGuacamoleResourceClosedException.java
@@ -0,0 +1,122 @@
+/*
+ * 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.language;
+
+import org.apache.guacamole.GuacamoleResourceClosedException;
+
+/**
+ * A {@link GuacamoleResourceClosedException} whose associated message is
+ * translatable and can be passed through an arbitrary translation service,
+ * producing a human-readable message in the user's native language.
+ */
+public class TranslatableGuacamoleResourceClosedException extends GuacamoleResourceClosedException implements Translatable {
+
+    /**
+     * A translatable, human-readable description of the exception that
+     * occurred.
+     */
+    private final TranslatableMessage translatableMessage;
+
+    /**
+     * Creates a new TranslatableGuacamoleResourceClosedException with the given
+     * message and cause. The message must be provided in both non-translatable
+     * (readable as-written) and translatable forms.
+     *
+     * @param message
+     *     A human-readable description of the exception that occurred. This
+     *     message should be readable on its own and as-written, without
+     *     requiring a translation service.
+     *
+     * @param translatableMessage
+     *     A translatable, human-readable description of the exception that
+     *     occurred.
+     *
+     * @param cause
+     *     The cause of this exception.
+     */
+    public TranslatableGuacamoleResourceClosedException(String message, TranslatableMessage translatableMessage, Throwable cause) {
+        super(message, cause);
+        this.translatableMessage = translatableMessage;
+    }
+
+    /**
+     * Creates a new TranslatableGuacamoleResourceClosedException with the given
+     * message. The message must be provided in both non-translatable (readable
+     * as-written) and translatable forms.
+     *
+     * @param message
+     *     A human-readable description of the exception that occurred. This
+     *     message should be readable on its own and as-written, without
+     *     requiring a translation service.
+     *
+     * @param translatableMessage
+     *     A translatable, human-readable description of the exception that
+     *     occurred.
+     */
+    public TranslatableGuacamoleResourceClosedException(String message, TranslatableMessage translatableMessage) {
+        super(message);
+        this.translatableMessage = translatableMessage;
+    }
+
+    /**
+     * Creates a new TranslatableGuacamoleResourceClosedException with the given
+     * message and cause. The message must be provided in both non-translatable
+     * (readable as-written) and translatable forms.
+     *
+     * @param message
+     *     A human-readable description of the exception that occurred. This
+     *     message should be readable on its own and as-written, without
+     *     requiring a translation service.
+     *
+     * @param key
+     *     The arbitrary key which can be used to look up the message to be
+     *     displayed in the user's native language.
+     *
+     * @param cause
+     *     The cause of this exception.
+     */
+    public TranslatableGuacamoleResourceClosedException(String message, String key, Throwable cause) {
+        this(message, new TranslatableMessage(key), cause);
+    }
+
+    /**
+     * Creates a new TranslatableGuacamoleResourceClosedException with the given
+     * message. The message must be provided in both non-translatable (readable
+     * as-written) and translatable forms.
+     *
+     * @param message
+     *     A human-readable description of the exception that occurred. This
+     *     message should be readable on its own and as-written, without
+     *     requiring a translation service.
+     *
+     * @param key
+     *     The arbitrary key which can be used to look up the message to be
+     *     displayed in the user's native language.
+     */
+    public TranslatableGuacamoleResourceClosedException(String message, String key) {
+        this(message, new TranslatableMessage(key));
+    }
+
+    @Override
+    public TranslatableMessage getTranslatableMessage() {
+        return translatableMessage;
+    }
+
+}
diff --git a/guacamole-ext/src/main/java/org/apache/guacamole/language/TranslatableGuacamoleResourceConflictException.java b/guacamole-ext/src/main/java/org/apache/guacamole/language/TranslatableGuacamoleResourceConflictException.java
new file mode 100644
index 0000000..e730206
--- /dev/null
+++ b/guacamole-ext/src/main/java/org/apache/guacamole/language/TranslatableGuacamoleResourceConflictException.java
@@ -0,0 +1,122 @@
+/*
+ * 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.language;
+
+import org.apache.guacamole.GuacamoleResourceConflictException;
+
+/**
+ * A {@link GuacamoleResourceConflictException} whose associated message is
+ * translatable and can be passed through an arbitrary translation service,
+ * producing a human-readable message in the user's native language.
+ */
+public class TranslatableGuacamoleResourceConflictException extends GuacamoleResourceConflictException implements Translatable {
+
+    /**
+     * A translatable, human-readable description of the exception that
+     * occurred.
+     */
+    private final TranslatableMessage translatableMessage;
+
+    /**
+     * Creates a new TranslatableGuacamoleResourceConflictException with the
+     * given message and cause. The message must be provided in both
+     * non-translatable (readable as-written) and translatable forms.
+     *
+     * @param message
+     *     A human-readable description of the exception that occurred. This
+     *     message should be readable on its own and as-written, without
+     *     requiring a translation service.
+     *
+     * @param translatableMessage
+     *     A translatable, human-readable description of the exception that
+     *     occurred.
+     *
+     * @param cause
+     *     The cause of this exception.
+     */
+    public TranslatableGuacamoleResourceConflictException(String message, TranslatableMessage translatableMessage, Throwable cause) {
+        super(message, cause);
+        this.translatableMessage = translatableMessage;
+    }
+
+    /**
+     * Creates a new TranslatableGuacamoleResourceConflictException with the
+     * given message. The message must be provided in both non-translatable
+     * (readable as-written) and translatable forms.
+     *
+     * @param message
+     *     A human-readable description of the exception that occurred. This
+     *     message should be readable on its own and as-written, without
+     *     requiring a translation service.
+     *
+     * @param translatableMessage
+     *     A translatable, human-readable description of the exception that
+     *     occurred.
+     */
+    public TranslatableGuacamoleResourceConflictException(String message, TranslatableMessage translatableMessage) {
+        super(message);
+        this.translatableMessage = translatableMessage;
+    }
+
+    /**
+     * Creates a new TranslatableGuacamoleResourceConflictException with the
+     * given message and cause. The message must be provided in both
+     * non-translatable (readable as-written) and translatable forms.
+     *
+     * @param message
+     *     A human-readable description of the exception that occurred. This
+     *     message should be readable on its own and as-written, without
+     *     requiring a translation service.
+     *
+     * @param key
+     *     The arbitrary key which can be used to look up the message to be
+     *     displayed in the user's native language.
+     *
+     * @param cause
+     *     The cause of this exception.
+     */
+    public TranslatableGuacamoleResourceConflictException(String message, String key, Throwable cause) {
+        this(message, new TranslatableMessage(key), cause);
+    }
+
+    /**
+     * Creates a new TranslatableGuacamoleResourceConflictException with the
+     * given message. The message must be provided in both non-translatable
+     * (readable as-written) and translatable forms.
+     *
+     * @param message
+     *     A human-readable description of the exception that occurred. This
+     *     message should be readable on its own and as-written, without
+     *     requiring a translation service.
+     *
+     * @param key
+     *     The arbitrary key which can be used to look up the message to be
+     *     displayed in the user's native language.
+     */
+    public TranslatableGuacamoleResourceConflictException(String message, String key) {
+        this(message, new TranslatableMessage(key));
+    }
+
+    @Override
+    public TranslatableMessage getTranslatableMessage() {
+        return translatableMessage;
+    }
+
+}
diff --git a/guacamole-ext/src/main/java/org/apache/guacamole/language/TranslatableGuacamoleResourceNotFoundException.java b/guacamole-ext/src/main/java/org/apache/guacamole/language/TranslatableGuacamoleResourceNotFoundException.java
new file mode 100644
index 0000000..138da0e
--- /dev/null
+++ b/guacamole-ext/src/main/java/org/apache/guacamole/language/TranslatableGuacamoleResourceNotFoundException.java
@@ -0,0 +1,122 @@
+/*
+ * 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.language;
+
+import org.apache.guacamole.GuacamoleResourceNotFoundException;
+
+/**
+ * A {@link GuacamoleResourceNotFoundException} whose associated message is
+ * translatable and can be passed through an arbitrary translation service,
+ * producing a human-readable message in the user's native language.
+ */
+public class TranslatableGuacamoleResourceNotFoundException extends GuacamoleResourceNotFoundException implements Translatable {
+
+    /**
+     * A translatable, human-readable description of the exception that
+     * occurred.
+     */
+    private final TranslatableMessage translatableMessage;
+
+    /**
+     * Creates a new TranslatableGuacamoleResourceNotFoundException with the
+     * given message and cause. The message must be provided in both
+     * non-translatable (readable as-written) and translatable forms.
+     *
+     * @param message
+     *     A human-readable description of the exception that occurred. This
+     *     message should be readable on its own and as-written, without
+     *     requiring a translation service.
+     *
+     * @param translatableMessage
+     *     A translatable, human-readable description of the exception that
+     *     occurred.
+     *
+     * @param cause
+     *     The cause of this exception.
+     */
+    public TranslatableGuacamoleResourceNotFoundException(String message, TranslatableMessage translatableMessage, Throwable cause) {
+        super(message, cause);
+        this.translatableMessage = translatableMessage;
+    }
+
+    /**
+     * Creates a new TranslatableGuacamoleResourceNotFoundException with the
+     * given message. The message must be provided in both non-translatable
+     * (readable as-written) and translatable forms.
+     *
+     * @param message
+     *     A human-readable description of the exception that occurred. This
+     *     message should be readable on its own and as-written, without
+     *     requiring a translation service.
+     *
+     * @param translatableMessage
+     *     A translatable, human-readable description of the exception that
+     *     occurred.
+     */
+    public TranslatableGuacamoleResourceNotFoundException(String message, TranslatableMessage translatableMessage) {
+        super(message);
+        this.translatableMessage = translatableMessage;
+    }
+
+    /**
+     * Creates a new TranslatableGuacamoleResourceNotFoundException with the
+     * given message and cause. The message must be provided in both
+     * non-translatable (readable as-written) and translatable forms.
+     *
+     * @param message
+     *     A human-readable description of the exception that occurred. This
+     *     message should be readable on its own and as-written, without
+     *     requiring a translation service.
+     *
+     * @param key
+     *     The arbitrary key which can be used to look up the message to be
+     *     displayed in the user's native language.
+     *
+     * @param cause
+     *     The cause of this exception.
+     */
+    public TranslatableGuacamoleResourceNotFoundException(String message, String key, Throwable cause) {
+        this(message, new TranslatableMessage(key), cause);
+    }
+
+    /**
+     * Creates a new TranslatableGuacamoleResourceNotFoundException with the
+     * given message. The message must be provided in both non-translatable
+     * (readable as-written) and translatable forms.
+     *
+     * @param message
+     *     A human-readable description of the exception that occurred. This
+     *     message should be readable on its own and as-written, without
+     *     requiring a translation service.
+     *
+     * @param key
+     *     The arbitrary key which can be used to look up the message to be
+     *     displayed in the user's native language.
+     */
+    public TranslatableGuacamoleResourceNotFoundException(String message, String key) {
+        this(message, new TranslatableMessage(key));
+    }
+
+    @Override
+    public TranslatableMessage getTranslatableMessage() {
+        return translatableMessage;
+    }
+
+}
diff --git a/guacamole-ext/src/main/java/org/apache/guacamole/language/TranslatableGuacamoleSecurityException.java b/guacamole-ext/src/main/java/org/apache/guacamole/language/TranslatableGuacamoleSecurityException.java
new file mode 100644
index 0000000..e6a9ecd
--- /dev/null
+++ b/guacamole-ext/src/main/java/org/apache/guacamole/language/TranslatableGuacamoleSecurityException.java
@@ -0,0 +1,122 @@
+/*
+ * 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.language;
+
+import org.apache.guacamole.GuacamoleSecurityException;
+
+/**
+ * A {@link GuacamoleSecurityException} whose associated message is translatable
+ * and can be passed through an arbitrary translation service, producing a
+ * human-readable message in the user's native language.
+ */
+public class TranslatableGuacamoleSecurityException extends GuacamoleSecurityException implements Translatable {
+
+    /**
+     * A translatable, human-readable description of the exception that
+     * occurred.
+     */
+    private final TranslatableMessage translatableMessage;
+
+    /**
+     * Creates a new TranslatableGuacamoleSecurityException with the given
+     * message and cause. The message must be provided in both non-translatable
+     * (readable as-written) and translatable forms.
+     *
+     * @param message
+     *     A human-readable description of the exception that occurred. This
+     *     message should be readable on its own and as-written, without
+     *     requiring a translation service.
+     *
+     * @param translatableMessage
+     *     A translatable, human-readable description of the exception that
+     *     occurred.
+     *
+     * @param cause
+     *     The cause of this exception.
+     */
+    public TranslatableGuacamoleSecurityException(String message, TranslatableMessage translatableMessage, Throwable cause) {
+        super(message, cause);
+        this.translatableMessage = translatableMessage;
+    }
+
+    /**
+     * Creates a new TranslatableGuacamoleSecurityException with the given
+     * message. The message must be provided in both non-translatable (readable
+     * as-written) and translatable forms.
+     *
+     * @param message
+     *     A human-readable description of the exception that occurred. This
+     *     message should be readable on its own and as-written, without
+     *     requiring a translation service.
+     *
+     * @param translatableMessage
+     *     A translatable, human-readable description of the exception that
+     *     occurred.
+     */
+    public TranslatableGuacamoleSecurityException(String message, TranslatableMessage translatableMessage) {
+        super(message);
+        this.translatableMessage = translatableMessage;
+    }
+
+    /**
+     * Creates a new TranslatableGuacamoleSecurityException with the given
+     * message and cause. The message must be provided in both non-translatable
+     * (readable as-written) and translatable forms.
+     *
+     * @param message
+     *     A human-readable description of the exception that occurred. This
+     *     message should be readable on its own and as-written, without
+     *     requiring a translation service.
+     *
+     * @param key
+     *     The arbitrary key which can be used to look up the message to be
+     *     displayed in the user's native language.
+     *
+     * @param cause
+     *     The cause of this exception.
+     */
+    public TranslatableGuacamoleSecurityException(String message, String key, Throwable cause) {
+        this(message, new TranslatableMessage(key), cause);
+    }
+
+    /**
+     * Creates a new TranslatableGuacamoleSecurityException with the given
+     * message. The message must be provided in both non-translatable (readable
+     * as-written) and translatable forms.
+     *
+     * @param message
+     *     A human-readable description of the exception that occurred. This
+     *     message should be readable on its own and as-written, without
+     *     requiring a translation service.
+     *
+     * @param key
+     *     The arbitrary key which can be used to look up the message to be
+     *     displayed in the user's native language.
+     */
+    public TranslatableGuacamoleSecurityException(String message, String key) {
+        this(message, new TranslatableMessage(key));
+    }
+
+    @Override
+    public TranslatableMessage getTranslatableMessage() {
+        return translatableMessage;
+    }
+
+}
diff --git a/guacamole-ext/src/main/java/org/apache/guacamole/language/TranslatableGuacamoleServerBusyException.java b/guacamole-ext/src/main/java/org/apache/guacamole/language/TranslatableGuacamoleServerBusyException.java
new file mode 100644
index 0000000..7d735b4
--- /dev/null
+++ b/guacamole-ext/src/main/java/org/apache/guacamole/language/TranslatableGuacamoleServerBusyException.java
@@ -0,0 +1,122 @@
+/*
+ * 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.language;
+
+import org.apache.guacamole.GuacamoleServerBusyException;
+
+/**
+ * A {@link GuacamoleServerBusyException} whose associated message is
+ * translatable and can be passed through an arbitrary translation service,
+ * producing a human-readable message in the user's native language.
+ */
+public class TranslatableGuacamoleServerBusyException extends GuacamoleServerBusyException implements Translatable {
+
+    /**
+     * A translatable, human-readable description of the exception that
+     * occurred.
+     */
+    private final TranslatableMessage translatableMessage;
+
+    /**
+     * Creates a new TranslatableGuacamoleServerBusyException with the given
+     * message and cause. The message must be provided in both non-translatable
+     * (readable as-written) and translatable forms.
+     *
+     * @param message
+     *     A human-readable description of the exception that occurred. This
+     *     message should be readable on its own and as-written, without
+     *     requiring a translation service.
+     *
+     * @param translatableMessage
+     *     A translatable, human-readable description of the exception that
+     *     occurred.
+     *
+     * @param cause
+     *     The cause of this exception.
+     */
+    public TranslatableGuacamoleServerBusyException(String message, TranslatableMessage translatableMessage, Throwable cause) {
+        super(message, cause);
+        this.translatableMessage = translatableMessage;
+    }
+
+    /**
+     * Creates a new TranslatableGuacamoleServerBusyException with the given
+     * message. The message must be provided in both non-translatable (readable
+     * as-written) and translatable forms.
+     *
+     * @param message
+     *     A human-readable description of the exception that occurred. This
+     *     message should be readable on its own and as-written, without
+     *     requiring a translation service.
+     *
+     * @param translatableMessage
+     *     A translatable, human-readable description of the exception that
+     *     occurred.
+     */
+    public TranslatableGuacamoleServerBusyException(String message, TranslatableMessage translatableMessage) {
+        super(message);
+        this.translatableMessage = translatableMessage;
+    }
+
+    /**
+     * Creates a new TranslatableGuacamoleServerBusyException with the given
+     * message and cause. The message must be provided in both non-translatable
+     * (readable as-written) and translatable forms.
+     *
+     * @param message
+     *     A human-readable description of the exception that occurred. This
+     *     message should be readable on its own and as-written, without
+     *     requiring a translation service.
+     *
+     * @param key
+     *     The arbitrary key which can be used to look up the message to be
+     *     displayed in the user's native language.
+     *
+     * @param cause
+     *     The cause of this exception.
+     */
+    public TranslatableGuacamoleServerBusyException(String message, String key, Throwable cause) {
+        this(message, new TranslatableMessage(key), cause);
+    }
+
+    /**
+     * Creates a new TranslatableGuacamoleServerBusyException with the given
+     * message. The message must be provided in both non-translatable (readable
+     * as-written) and translatable forms.
+     *
+     * @param message
+     *     A human-readable description of the exception that occurred. This
+     *     message should be readable on its own and as-written, without
+     *     requiring a translation service.
+     *
+     * @param key
+     *     The arbitrary key which can be used to look up the message to be
+     *     displayed in the user's native language.
+     */
+    public TranslatableGuacamoleServerBusyException(String message, String key) {
+        this(message, new TranslatableMessage(key));
+    }
+
+    @Override
+    public TranslatableMessage getTranslatableMessage() {
+        return translatableMessage;
+    }
+
+}
diff --git a/guacamole-ext/src/main/java/org/apache/guacamole/language/TranslatableGuacamoleServerException.java b/guacamole-ext/src/main/java/org/apache/guacamole/language/TranslatableGuacamoleServerException.java
new file mode 100644
index 0000000..686498c
--- /dev/null
+++ b/guacamole-ext/src/main/java/org/apache/guacamole/language/TranslatableGuacamoleServerException.java
@@ -0,0 +1,122 @@
+/*
+ * 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.language;
+
+import org.apache.guacamole.GuacamoleServerException;
+
+/**
+ * A {@link GuacamoleServerException} whose associated message is translatable
+ * and can be passed through an arbitrary translation service, producing a
+ * human-readable message in the user's native language.
+ */
+public class TranslatableGuacamoleServerException extends GuacamoleServerException implements Translatable {
+
+    /**
+     * A translatable, human-readable description of the exception that
+     * occurred.
+     */
+    private final TranslatableMessage translatableMessage;
+
+    /**
+     * Creates a new TranslatableGuacamoleServerException with the given
+     * message and cause. The message must be provided in both non-translatable
+     * (readable as-written) and translatable forms.
+     *
+     * @param message
+     *     A human-readable description of the exception that occurred. This
+     *     message should be readable on its own and as-written, without
+     *     requiring a translation service.
+     *
+     * @param translatableMessage
+     *     A translatable, human-readable description of the exception that
+     *     occurred.
+     *
+     * @param cause
+     *     The cause of this exception.
+     */
+    public TranslatableGuacamoleServerException(String message, TranslatableMessage translatableMessage, Throwable cause) {
+        super(message, cause);
+        this.translatableMessage = translatableMessage;
+    }
+
+    /**
+     * Creates a new TranslatableGuacamoleServerException with the given
+     * message. The message must be provided in both non-translatable (readable
+     * as-written) and translatable forms.
+     *
+     * @param message
+     *     A human-readable description of the exception that occurred. This
+     *     message should be readable on its own and as-written, without
+     *     requiring a translation service.
+     *
+     * @param translatableMessage
+     *     A translatable, human-readable description of the exception that
+     *     occurred.
+     */
+    public TranslatableGuacamoleServerException(String message, TranslatableMessage translatableMessage) {
+        super(message);
+        this.translatableMessage = translatableMessage;
+    }
+
+    /**
+     * Creates a new TranslatableGuacamoleServerException with the given
+     * message and cause. The message must be provided in both non-translatable
+     * (readable as-written) and translatable forms.
+     *
+     * @param message
+     *     A human-readable description of the exception that occurred. This
+     *     message should be readable on its own and as-written, without
+     *     requiring a translation service.
+     *
+     * @param key
+     *     The arbitrary key which can be used to look up the message to be
+     *     displayed in the user's native language.
+     *
+     * @param cause
+     *     The cause of this exception.
+     */
+    public TranslatableGuacamoleServerException(String message, String key, Throwable cause) {
+        this(message, new TranslatableMessage(key), cause);
+    }
+
+    /**
+     * Creates a new TranslatableGuacamoleServerException with the given
+     * message. The message must be provided in both non-translatable (readable
+     * as-written) and translatable forms.
+     *
+     * @param message
+     *     A human-readable description of the exception that occurred. This
+     *     message should be readable on its own and as-written, without
+     *     requiring a translation service.
+     *
+     * @param key
+     *     The arbitrary key which can be used to look up the message to be
+     *     displayed in the user's native language.
+     */
+    public TranslatableGuacamoleServerException(String message, String key) {
+        this(message, new TranslatableMessage(key));
+    }
+
+    @Override
+    public TranslatableMessage getTranslatableMessage() {
+        return translatableMessage;
+    }
+
+}
diff --git a/guacamole-ext/src/main/java/org/apache/guacamole/language/TranslatableGuacamoleSessionClosedException.java b/guacamole-ext/src/main/java/org/apache/guacamole/language/TranslatableGuacamoleSessionClosedException.java
new file mode 100644
index 0000000..ca9a025
--- /dev/null
+++ b/guacamole-ext/src/main/java/org/apache/guacamole/language/TranslatableGuacamoleSessionClosedException.java
@@ -0,0 +1,122 @@
+/*
+ * 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.language;
+
+import org.apache.guacamole.GuacamoleSessionClosedException;
+
+/**
+ * A {@link GuacamoleSessionClosedException} whose associated message is
+ * translatable and can be passed through an arbitrary translation service,
+ * producing a human-readable message in the user's native language.
+ */
+public class TranslatableGuacamoleSessionClosedException extends GuacamoleSessionClosedException implements Translatable {
+
+    /**
+     * A translatable, human-readable description of the exception that
+     * occurred.
+     */
+    private final TranslatableMessage translatableMessage;
+
+    /**
+     * Creates a new TranslatableGuacamoleSessionClosedException with the given
+     * message and cause. The message must be provided in both non-translatable
+     * (readable as-written) and translatable forms.
+     *
+     * @param message
+     *     A human-readable description of the exception that occurred. This
+     *     message should be readable on its own and as-written, without
+     *     requiring a translation service.
+     *
+     * @param translatableMessage
+     *     A translatable, human-readable description of the exception that
+     *     occurred.
+     *
+     * @param cause
+     *     The cause of this exception.
+     */
+    public TranslatableGuacamoleSessionClosedException(String message, TranslatableMessage translatableMessage, Throwable cause) {
+        super(message, cause);
+        this.translatableMessage = translatableMessage;
+    }
+
+    /**
+     * Creates a new TranslatableGuacamoleSessionClosedException with the given
+     * message. The message must be provided in both non-translatable (readable
+     * as-written) and translatable forms.
+     *
+     * @param message
+     *     A human-readable description of the exception that occurred. This
+     *     message should be readable on its own and as-written, without
+     *     requiring a translation service.
+     *
+     * @param translatableMessage
+     *     A translatable, human-readable description of the exception that
+     *     occurred.
+     */
+    public TranslatableGuacamoleSessionClosedException(String message, TranslatableMessage translatableMessage) {
+        super(message);
+        this.translatableMessage = translatableMessage;
+    }
+
+    /**
+     * Creates a new TranslatableGuacamoleSessionClosedException with the given
+     * message and cause. The message must be provided in both non-translatable
+     * (readable as-written) and translatable forms.
+     *
+     * @param message
+     *     A human-readable description of the exception that occurred. This
+     *     message should be readable on its own and as-written, without
+     *     requiring a translation service.
+     *
+     * @param key
+     *     The arbitrary key which can be used to look up the message to be
+     *     displayed in the user's native language.
+     *
+     * @param cause
+     *     The cause of this exception.
+     */
+    public TranslatableGuacamoleSessionClosedException(String message, String key, Throwable cause) {
+        this(message, new TranslatableMessage(key), cause);
+    }
+
+    /**
+     * Creates a new TranslatableGuacamoleSessionClosedException with the given
+     * message. The message must be provided in both non-translatable (readable
+     * as-written) and translatable forms.
+     *
+     * @param message
+     *     A human-readable description of the exception that occurred. This
+     *     message should be readable on its own and as-written, without
+     *     requiring a translation service.
+     *
+     * @param key
+     *     The arbitrary key which can be used to look up the message to be
+     *     displayed in the user's native language.
+     */
+    public TranslatableGuacamoleSessionClosedException(String message, String key) {
+        this(message, new TranslatableMessage(key));
+    }
+
+    @Override
+    public TranslatableMessage getTranslatableMessage() {
+        return translatableMessage;
+    }
+
+}
diff --git a/guacamole-ext/src/main/java/org/apache/guacamole/language/TranslatableGuacamoleSessionConflictException.java b/guacamole-ext/src/main/java/org/apache/guacamole/language/TranslatableGuacamoleSessionConflictException.java
new file mode 100644
index 0000000..b20575b
--- /dev/null
+++ b/guacamole-ext/src/main/java/org/apache/guacamole/language/TranslatableGuacamoleSessionConflictException.java
@@ -0,0 +1,122 @@
+/*
+ * 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.language;
+
+import org.apache.guacamole.GuacamoleSessionConflictException;
+
+/**
+ * A {@link GuacamoleSessionConflictException} whose associated message is
+ * translatable and can be passed through an arbitrary translation service,
+ * producing a human-readable message in the user's native language.
+ */
+public class TranslatableGuacamoleSessionConflictException extends GuacamoleSessionConflictException implements Translatable {
+
+    /**
+     * A translatable, human-readable description of the exception that
+     * occurred.
+     */
+    private final TranslatableMessage translatableMessage;
+
+    /**
+     * Creates a new TranslatableGuacamoleSessionConflictException with the
+     * given message and cause. The message must be provided in both
+     * non-translatable (readable as-written) and translatable forms.
+     *
+     * @param message
+     *     A human-readable description of the exception that occurred. This
+     *     message should be readable on its own and as-written, without
+     *     requiring a translation service.
+     *
+     * @param translatableMessage
+     *     A translatable, human-readable description of the exception that
+     *     occurred.
+     *
+     * @param cause
+     *     The cause of this exception.
+     */
+    public TranslatableGuacamoleSessionConflictException(String message, TranslatableMessage translatableMessage, Throwable cause) {
+        super(message, cause);
+        this.translatableMessage = translatableMessage;
+    }
+
+    /**
+     * Creates a new TranslatableGuacamoleSessionConflictException with the
+     * given message. The message must be provided in both non-translatable
+     * (readable as-written) and translatable forms.
+     *
+     * @param message
+     *     A human-readable description of the exception that occurred. This
+     *     message should be readable on its own and as-written, without
+     *     requiring a translation service.
+     *
+     * @param translatableMessage
+     *     A translatable, human-readable description of the exception that
+     *     occurred.
+     */
+    public TranslatableGuacamoleSessionConflictException(String message, TranslatableMessage translatableMessage) {
+        super(message);
+        this.translatableMessage = translatableMessage;
+    }
+
+    /**
+     * Creates a new TranslatableGuacamoleSessionConflictException with the
+     * given message and cause. The message must be provided in both
+     * non-translatable (readable as-written) and translatable forms.
+     *
+     * @param message
+     *     A human-readable description of the exception that occurred. This
+     *     message should be readable on its own and as-written, without
+     *     requiring a translation service.
+     *
+     * @param key
+     *     The arbitrary key which can be used to look up the message to be
+     *     displayed in the user's native language.
+     *
+     * @param cause
+     *     The cause of this exception.
+     */
+    public TranslatableGuacamoleSessionConflictException(String message, String key, Throwable cause) {
+        this(message, new TranslatableMessage(key), cause);
+    }
+
+    /**
+     * Creates a new TranslatableGuacamoleSessionConflictException with the
+     * given message. The message must be provided in both non-translatable
+     * (readable as-written) and translatable forms.
+     *
+     * @param message
+     *     A human-readable description of the exception that occurred. This
+     *     message should be readable on its own and as-written, without
+     *     requiring a translation service.
+     *
+     * @param key
+     *     The arbitrary key which can be used to look up the message to be
+     *     displayed in the user's native language.
+     */
+    public TranslatableGuacamoleSessionConflictException(String message, String key) {
+        this(message, new TranslatableMessage(key));
+    }
+
+    @Override
+    public TranslatableMessage getTranslatableMessage() {
+        return translatableMessage;
+    }
+
+}
diff --git a/guacamole-ext/src/main/java/org/apache/guacamole/language/TranslatableGuacamoleSessionTimeoutException.java b/guacamole-ext/src/main/java/org/apache/guacamole/language/TranslatableGuacamoleSessionTimeoutException.java
new file mode 100644
index 0000000..700a3ee
--- /dev/null
+++ b/guacamole-ext/src/main/java/org/apache/guacamole/language/TranslatableGuacamoleSessionTimeoutException.java
@@ -0,0 +1,122 @@
+/*
+ * 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.language;
+
+import org.apache.guacamole.GuacamoleSessionTimeoutException;
+
+/**
+ * A {@link GuacamoleSessionTimeoutException} whose associated message is
+ * translatable and can be passed through an arbitrary translation service,
+ * producing a human-readable message in the user's native language.
+ */
+public class TranslatableGuacamoleSessionTimeoutException extends GuacamoleSessionTimeoutException implements Translatable {
+
+    /**
+     * A translatable, human-readable description of the exception that
+     * occurred.
+     */
+    private final TranslatableMessage translatableMessage;
+
+    /**
+     * Creates a new TranslatableGuacamoleSessionTimeoutException with the given
+     * message and cause. The message must be provided in both non-translatable
+     * (readable as-written) and translatable forms.
+     *
+     * @param message
+     *     A human-readable description of the exception that occurred. This
+     *     message should be readable on its own and as-written, without
+     *     requiring a translation service.
+     *
+     * @param translatableMessage
+     *     A translatable, human-readable description of the exception that
+     *     occurred.
+     *
+     * @param cause
+     *     The cause of this exception.
+     */
+    public TranslatableGuacamoleSessionTimeoutException(String message, TranslatableMessage translatableMessage, Throwable cause) {
+        super(message, cause);
+        this.translatableMessage = translatableMessage;
+    }
+
+    /**
+     * Creates a new TranslatableGuacamoleSessionTimeoutException with the given
+     * message. The message must be provided in both non-translatable (readable
+     * as-written) and translatable forms.
+     *
+     * @param message
+     *     A human-readable description of the exception that occurred. This
+     *     message should be readable on its own and as-written, without
+     *     requiring a translation service.
+     *
+     * @param translatableMessage
+     *     A translatable, human-readable description of the exception that
+     *     occurred.
+     */
+    public TranslatableGuacamoleSessionTimeoutException(String message, TranslatableMessage translatableMessage) {
+        super(message);
+        this.translatableMessage = translatableMessage;
+    }
+
+    /**
+     * Creates a new TranslatableGuacamoleSessionTimeoutException with the given
+     * message and cause. The message must be provided in both non-translatable
+     * (readable as-written) and translatable forms.
+     *
+     * @param message
+     *     A human-readable description of the exception that occurred. This
+     *     message should be readable on its own and as-written, without
+     *     requiring a translation service.
+     *
+     * @param key
+     *     The arbitrary key which can be used to look up the message to be
+     *     displayed in the user's native language.
+     *
+     * @param cause
+     *     The cause of this exception.
+     */
+    public TranslatableGuacamoleSessionTimeoutException(String message, String key, Throwable cause) {
+        this(message, new TranslatableMessage(key), cause);
+    }
+
+    /**
+     * Creates a new TranslatableGuacamoleSessionTimeoutException with the given
+     * message. The message must be provided in both non-translatable (readable
+     * as-written) and translatable forms.
+     *
+     * @param message
+     *     A human-readable description of the exception that occurred. This
+     *     message should be readable on its own and as-written, without
+     *     requiring a translation service.
+     *
+     * @param key
+     *     The arbitrary key which can be used to look up the message to be
+     *     displayed in the user's native language.
+     */
+    public TranslatableGuacamoleSessionTimeoutException(String message, String key) {
+        this(message, new TranslatableMessage(key));
+    }
+
+    @Override
+    public TranslatableMessage getTranslatableMessage() {
+        return translatableMessage;
+    }
+
+}
diff --git a/guacamole-ext/src/main/java/org/apache/guacamole/language/TranslatableGuacamoleUnauthorizedException.java b/guacamole-ext/src/main/java/org/apache/guacamole/language/TranslatableGuacamoleUnauthorizedException.java
new file mode 100644
index 0000000..340aced
--- /dev/null
+++ b/guacamole-ext/src/main/java/org/apache/guacamole/language/TranslatableGuacamoleUnauthorizedException.java
@@ -0,0 +1,122 @@
+/*
+ * 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.language;
+
+import org.apache.guacamole.GuacamoleUnauthorizedException;
+
+/**
+ * A {@link GuacamoleUnauthorizedException} whose associated message is
+ * translatable and can be passed through an arbitrary translation service,
+ * producing a human-readable message in the user's native language.
+ */
+public class TranslatableGuacamoleUnauthorizedException extends GuacamoleUnauthorizedException implements Translatable {
+
+    /**
+     * A translatable, human-readable description of the exception that
+     * occurred.
+     */
+    private final TranslatableMessage translatableMessage;
+
+    /**
+     * Creates a new TranslatableGuacamoleUnauthorizedException with the given
+     * message and cause. The message must be provided in both non-translatable
+     * (readable as-written) and translatable forms.
+     *
+     * @param message
+     *     A human-readable description of the exception that occurred. This
+     *     message should be readable on its own and as-written, without
+     *     requiring a translation service.
+     *
+     * @param translatableMessage
+     *     A translatable, human-readable description of the exception that
+     *     occurred.
+     *
+     * @param cause
+     *     The cause of this exception.
+     */
+    public TranslatableGuacamoleUnauthorizedException(String message, TranslatableMessage translatableMessage, Throwable cause) {
+        super(message, cause);
+        this.translatableMessage = translatableMessage;
+    }
+
+    /**
+     * Creates a new TranslatableGuacamoleUnauthorizedException with the given
+     * message. The message must be provided in both non-translatable (readable
+     * as-written) and translatable forms.
+     *
+     * @param message
+     *     A human-readable description of the exception that occurred. This
+     *     message should be readable on its own and as-written, without
+     *     requiring a translation service.
+     *
+     * @param translatableMessage
+     *     A translatable, human-readable description of the exception that
+     *     occurred.
+     */
+    public TranslatableGuacamoleUnauthorizedException(String message, TranslatableMessage translatableMessage) {
+        super(message);
+        this.translatableMessage = translatableMessage;
+    }
+
+    /**
+     * Creates a new TranslatableGuacamoleUnauthorizedException with the given
+     * message and cause. The message must be provided in both non-translatable
+     * (readable as-written) and translatable forms.
+     *
+     * @param message
+     *     A human-readable description of the exception that occurred. This
+     *     message should be readable on its own and as-written, without
+     *     requiring a translation service.
+     *
+     * @param key
+     *     The arbitrary key which can be used to look up the message to be
+     *     displayed in the user's native language.
+     *
+     * @param cause
+     *     The cause of this exception.
+     */
+    public TranslatableGuacamoleUnauthorizedException(String message, String key, Throwable cause) {
+        this(message, new TranslatableMessage(key), cause);
+    }
+
+    /**
+     * Creates a new TranslatableGuacamoleUnauthorizedException with the given
+     * message. The message must be provided in both non-translatable (readable
+     * as-written) and translatable forms.
+     *
+     * @param message
+     *     A human-readable description of the exception that occurred. This
+     *     message should be readable on its own and as-written, without
+     *     requiring a translation service.
+     *
+     * @param key
+     *     The arbitrary key which can be used to look up the message to be
+     *     displayed in the user's native language.
+     */
+    public TranslatableGuacamoleUnauthorizedException(String message, String key) {
+        this(message, new TranslatableMessage(key));
+    }
+
+    @Override
+    public TranslatableMessage getTranslatableMessage() {
+        return translatableMessage;
+    }
+
+}
diff --git a/guacamole-ext/src/main/java/org/apache/guacamole/language/TranslatableGuacamoleUnsupportedException.java b/guacamole-ext/src/main/java/org/apache/guacamole/language/TranslatableGuacamoleUnsupportedException.java
new file mode 100644
index 0000000..184f65a
--- /dev/null
+++ b/guacamole-ext/src/main/java/org/apache/guacamole/language/TranslatableGuacamoleUnsupportedException.java
@@ -0,0 +1,122 @@
+/*
+ * 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.language;
+
+import org.apache.guacamole.GuacamoleUnsupportedException;
+
+/**
+ * A {@link GuacamoleUnsupportedException} whose associated message is
+ * translatable and can be passed through an arbitrary translation service,
+ * producing a human-readable message in the user's native language.
+ */
+public class TranslatableGuacamoleUnsupportedException extends GuacamoleUnsupportedException implements Translatable {
+
+    /**
+     * A translatable, human-readable description of the exception that
+     * occurred.
+     */
+    private final TranslatableMessage translatableMessage;
+
+    /**
+     * Creates a new TranslatableGuacamoleUnsupportedException with the given
+     * message and cause. The message must be provided in both non-translatable
+     * (readable as-written) and translatable forms.
+     *
+     * @param message
+     *     A human-readable description of the exception that occurred. This
+     *     message should be readable on its own and as-written, without
+     *     requiring a translation service.
+     *
+     * @param translatableMessage
+     *     A translatable, human-readable description of the exception that
+     *     occurred.
+     *
+     * @param cause
+     *     The cause of this exception.
+     */
+    public TranslatableGuacamoleUnsupportedException(String message, TranslatableMessage translatableMessage, Throwable cause) {
+        super(message, cause);
+        this.translatableMessage = translatableMessage;
+    }
+
+    /**
+     * Creates a new TranslatableGuacamoleUnsupportedException with the given
+     * message. The message must be provided in both non-translatable (readable
+     * as-written) and translatable forms.
+     *
+     * @param message
+     *     A human-readable description of the exception that occurred. This
+     *     message should be readable on its own and as-written, without
+     *     requiring a translation service.
+     *
+     * @param translatableMessage
+     *     A translatable, human-readable description of the exception that
+     *     occurred.
+     */
+    public TranslatableGuacamoleUnsupportedException(String message, TranslatableMessage translatableMessage) {
+        super(message);
+        this.translatableMessage = translatableMessage;
+    }
+
+    /**
+     * Creates a new TranslatableGuacamoleUnsupportedException with the given
+     * message and cause. The message must be provided in both non-translatable
+     * (readable as-written) and translatable forms.
+     *
+     * @param message
+     *     A human-readable description of the exception that occurred. This
+     *     message should be readable on its own and as-written, without
+     *     requiring a translation service.
+     *
+     * @param key
+     *     The arbitrary key which can be used to look up the message to be
+     *     displayed in the user's native language.
+     *
+     * @param cause
+     *     The cause of this exception.
+     */
+    public TranslatableGuacamoleUnsupportedException(String message, String key, Throwable cause) {
+        this(message, new TranslatableMessage(key), cause);
+    }
+
+    /**
+     * Creates a new TranslatableGuacamoleUnsupportedException with the given
+     * message. The message must be provided in both non-translatable (readable
+     * as-written) and translatable forms.
+     *
+     * @param message
+     *     A human-readable description of the exception that occurred. This
+     *     message should be readable on its own and as-written, without
+     *     requiring a translation service.
+     *
+     * @param key
+     *     The arbitrary key which can be used to look up the message to be
+     *     displayed in the user's native language.
+     */
+    public TranslatableGuacamoleUnsupportedException(String message, String key) {
+        this(message, new TranslatableMessage(key));
+    }
+
+    @Override
+    public TranslatableMessage getTranslatableMessage() {
+        return translatableMessage;
+    }
+
+}
diff --git a/guacamole-ext/src/main/java/org/apache/guacamole/language/TranslatableGuacamoleUpstreamException.java b/guacamole-ext/src/main/java/org/apache/guacamole/language/TranslatableGuacamoleUpstreamException.java
new file mode 100644
index 0000000..0122acb
--- /dev/null
+++ b/guacamole-ext/src/main/java/org/apache/guacamole/language/TranslatableGuacamoleUpstreamException.java
@@ -0,0 +1,122 @@
+/*
+ * 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.language;
+
+import org.apache.guacamole.GuacamoleUpstreamException;
+
+/**
+ * A {@link GuacamoleUpstreamException} whose associated message is translatable
+ * and can be passed through an arbitrary translation service, producing a
+ * human-readable message in the user's native language.
+ */
+public class TranslatableGuacamoleUpstreamException extends GuacamoleUpstreamException implements Translatable {
+
+    /**
+     * A translatable, human-readable description of the exception that
+     * occurred.
+     */
+    private final TranslatableMessage translatableMessage;
+
+    /**
+     * Creates a new TranslatableGuacamoleUpstreamException with the given
+     * message and cause. The message must be provided in both non-translatable
+     * (readable as-written) and translatable forms.
+     *
+     * @param message
+     *     A human-readable description of the exception that occurred. This
+     *     message should be readable on its own and as-written, without
+     *     requiring a translation service.
+     *
+     * @param translatableMessage
+     *     A translatable, human-readable description of the exception that
+     *     occurred.
+     *
+     * @param cause
+     *     The cause of this exception.
+     */
+    public TranslatableGuacamoleUpstreamException(String message, TranslatableMessage translatableMessage, Throwable cause) {
+        super(message, cause);
+        this.translatableMessage = translatableMessage;
+    }
+
+    /**
+     * Creates a new TranslatableGuacamoleUpstreamException with the given
+     * message. The message must be provided in both non-translatable (readable
+     * as-written) and translatable forms.
+     *
+     * @param message
+     *     A human-readable description of the exception that occurred. This
+     *     message should be readable on its own and as-written, without
+     *     requiring a translation service.
+     *
+     * @param translatableMessage
+     *     A translatable, human-readable description of the exception that
+     *     occurred.
+     */
+    public TranslatableGuacamoleUpstreamException(String message, TranslatableMessage translatableMessage) {
+        super(message);
+        this.translatableMessage = translatableMessage;
+    }
+
+    /**
+     * Creates a new TranslatableGuacamoleUpstreamException with the given
+     * message and cause. The message must be provided in both non-translatable
+     * (readable as-written) and translatable forms.
+     *
+     * @param message
+     *     A human-readable description of the exception that occurred. This
+     *     message should be readable on its own and as-written, without
+     *     requiring a translation service.
+     *
+     * @param key
+     *     The arbitrary key which can be used to look up the message to be
+     *     displayed in the user's native language.
+     *
+     * @param cause
+     *     The cause of this exception.
+     */
+    public TranslatableGuacamoleUpstreamException(String message, String key, Throwable cause) {
+        this(message, new TranslatableMessage(key), cause);
+    }
+
+    /**
+     * Creates a new TranslatableGuacamoleUpstreamException with the given
+     * message. The message must be provided in both non-translatable (readable
+     * as-written) and translatable forms.
+     *
+     * @param message
+     *     A human-readable description of the exception that occurred. This
+     *     message should be readable on its own and as-written, without
+     *     requiring a translation service.
+     *
+     * @param key
+     *     The arbitrary key which can be used to look up the message to be
+     *     displayed in the user's native language.
+     */
+    public TranslatableGuacamoleUpstreamException(String message, String key) {
+        this(message, new TranslatableMessage(key));
+    }
+
+    @Override
+    public TranslatableMessage getTranslatableMessage() {
+        return translatableMessage;
+    }
+
+}
diff --git a/guacamole-ext/src/main/java/org/apache/guacamole/language/TranslatableGuacamoleUpstreamNotFoundException.java b/guacamole-ext/src/main/java/org/apache/guacamole/language/TranslatableGuacamoleUpstreamNotFoundException.java
new file mode 100644
index 0000000..e9b3214
--- /dev/null
+++ b/guacamole-ext/src/main/java/org/apache/guacamole/language/TranslatableGuacamoleUpstreamNotFoundException.java
@@ -0,0 +1,122 @@
+/*
+ * 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.language;
+
+import org.apache.guacamole.GuacamoleUpstreamNotFoundException;
+
+/**
+ * A {@link GuacamoleUpstreamNotFoundException} whose associated message is
+ * translatable and can be passed through an arbitrary translation service,
+ * producing a human-readable message in the user's native language.
+ */
+public class TranslatableGuacamoleUpstreamNotFoundException extends GuacamoleUpstreamNotFoundException implements Translatable {
+
+    /**
+     * A translatable, human-readable description of the exception that
+     * occurred.
+     */
+    private final TranslatableMessage translatableMessage;
+
+    /**
+     * Creates a new TranslatableGuacamoleUpstreamNotFoundException with the
+     * given message and cause. The message must be provided in both
+     * non-translatable (readable as-written) and translatable forms.
+     *
+     * @param message
+     *     A human-readable description of the exception that occurred. This
+     *     message should be readable on its own and as-written, without
+     *     requiring a translation service.
+     *
+     * @param translatableMessage
+     *     A translatable, human-readable description of the exception that
+     *     occurred.
+     *
+     * @param cause
+     *     The cause of this exception.
+     */
+    public TranslatableGuacamoleUpstreamNotFoundException(String message, TranslatableMessage translatableMessage, Throwable cause) {
+        super(message, cause);
+        this.translatableMessage = translatableMessage;
+    }
+
+    /**
+     * Creates a new TranslatableGuacamoleUpstreamNotFoundException with the
+     * given message. The message must be provided in both non-translatable
+     * (readable as-written) and translatable forms.
+     *
+     * @param message
+     *     A human-readable description of the exception that occurred. This
+     *     message should be readable on its own and as-written, without
+     *     requiring a translation service.
+     *
+     * @param translatableMessage
+     *     A translatable, human-readable description of the exception that
+     *     occurred.
+     */
+    public TranslatableGuacamoleUpstreamNotFoundException(String message, TranslatableMessage translatableMessage) {
+        super(message);
+        this.translatableMessage = translatableMessage;
+    }
+
+    /**
+     * Creates a new TranslatableGuacamoleUpstreamNotFoundException with the
+     * given message and cause. The message must be provided in both
+     * non-translatable (readable as-written) and translatable forms.
+     *
+     * @param message
+     *     A human-readable description of the exception that occurred. This
+     *     message should be readable on its own and as-written, without
+     *     requiring a translation service.
+     *
+     * @param key
+     *     The arbitrary key which can be used to look up the message to be
+     *     displayed in the user's native language.
+     *
+     * @param cause
+     *     The cause of this exception.
+     */
+    public TranslatableGuacamoleUpstreamNotFoundException(String message, String key, Throwable cause) {
+        this(message, new TranslatableMessage(key), cause);
+    }
+
+    /**
+     * Creates a new TranslatableGuacamoleUpstreamNotFoundException with the
+     * given message. The message must be provided in both non-translatable
+     * (readable as-written) and translatable forms.
+     *
+     * @param message
+     *     A human-readable description of the exception that occurred. This
+     *     message should be readable on its own and as-written, without
+     *     requiring a translation service.
+     *
+     * @param key
+     *     The arbitrary key which can be used to look up the message to be
+     *     displayed in the user's native language.
+     */
+    public TranslatableGuacamoleUpstreamNotFoundException(String message, String key) {
+        this(message, new TranslatableMessage(key));
+    }
+
+    @Override
+    public TranslatableMessage getTranslatableMessage() {
+        return translatableMessage;
+    }
+
+}
diff --git a/guacamole-ext/src/main/java/org/apache/guacamole/language/TranslatableGuacamoleUpstreamTimeoutException.java b/guacamole-ext/src/main/java/org/apache/guacamole/language/TranslatableGuacamoleUpstreamTimeoutException.java
new file mode 100644
index 0000000..9cac87c
--- /dev/null
+++ b/guacamole-ext/src/main/java/org/apache/guacamole/language/TranslatableGuacamoleUpstreamTimeoutException.java
@@ -0,0 +1,122 @@
+/*
+ * 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.language;
+
+import org.apache.guacamole.GuacamoleUpstreamTimeoutException;
+
+/**
+ * A {@link GuacamoleUpstreamTimeoutException} whose associated message is
+ * translatable and can be passed through an arbitrary translation service,
+ * producing a human-readable message in the user's native language.
+ */
+public class TranslatableGuacamoleUpstreamTimeoutException extends GuacamoleUpstreamTimeoutException implements Translatable {
+
+    /**
+     * A translatable, human-readable description of the exception that
+     * occurred.
+     */
+    private final TranslatableMessage translatableMessage;
+
+    /**
+     * Creates a new TranslatableGuacamoleUpstreamTimeoutException with the
+     * given message and cause. The message must be provided in both
+     * non-translatable (readable as-written) and translatable forms.
+     *
+     * @param message
+     *     A human-readable description of the exception that occurred. This
+     *     message should be readable on its own and as-written, without
+     *     requiring a translation service.
+     *
+     * @param translatableMessage
+     *     A translatable, human-readable description of the exception that
+     *     occurred.
+     *
+     * @param cause
+     *     The cause of this exception.
+     */
+    public TranslatableGuacamoleUpstreamTimeoutException(String message, TranslatableMessage translatableMessage, Throwable cause) {
+        super(message, cause);
+        this.translatableMessage = translatableMessage;
+    }
+
+    /**
+     * Creates a new TranslatableGuacamoleUpstreamTimeoutException with the
+     * given message. The message must be provided in both non-translatable
+     * (readable as-written) and translatable forms.
+     *
+     * @param message
+     *     A human-readable description of the exception that occurred. This
+     *     message should be readable on its own and as-written, without
+     *     requiring a translation service.
+     *
+     * @param translatableMessage
+     *     A translatable, human-readable description of the exception that
+     *     occurred.
+     */
+    public TranslatableGuacamoleUpstreamTimeoutException(String message, TranslatableMessage translatableMessage) {
+        super(message);
+        this.translatableMessage = translatableMessage;
+    }
+
+    /**
+     * Creates a new TranslatableGuacamoleUpstreamTimeoutException with the
+     * given message and cause. The message must be provided in both
+     * non-translatable (readable as-written) and translatable forms.
+     *
+     * @param message
+     *     A human-readable description of the exception that occurred. This
+     *     message should be readable on its own and as-written, without
+     *     requiring a translation service.
+     *
+     * @param key
+     *     The arbitrary key which can be used to look up the message to be
+     *     displayed in the user's native language.
+     *
+     * @param cause
+     *     The cause of this exception.
+     */
+    public TranslatableGuacamoleUpstreamTimeoutException(String message, String key, Throwable cause) {
+        this(message, new TranslatableMessage(key), cause);
+    }
+
+    /**
+     * Creates a new TranslatableGuacamoleUpstreamTimeoutException with the
+     * given message. The message must be provided in both non-translatable
+     * (readable as-written) and translatable forms.
+     *
+     * @param message
+     *     A human-readable description of the exception that occurred. This
+     *     message should be readable on its own and as-written, without
+     *     requiring a translation service.
+     *
+     * @param key
+     *     The arbitrary key which can be used to look up the message to be
+     *     displayed in the user's native language.
+     */
+    public TranslatableGuacamoleUpstreamTimeoutException(String message, String key) {
+        this(message, new TranslatableMessage(key));
+    }
+
+    @Override
+    public TranslatableMessage getTranslatableMessage() {
+        return translatableMessage;
+    }
+
+}
diff --git a/guacamole-ext/src/main/java/org/apache/guacamole/language/TranslatableGuacamoleUpstreamUnavailableException.java b/guacamole-ext/src/main/java/org/apache/guacamole/language/TranslatableGuacamoleUpstreamUnavailableException.java
new file mode 100644
index 0000000..2962eb6
--- /dev/null
+++ b/guacamole-ext/src/main/java/org/apache/guacamole/language/TranslatableGuacamoleUpstreamUnavailableException.java
@@ -0,0 +1,122 @@
+/*
+ * 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.language;
+
+import org.apache.guacamole.GuacamoleUpstreamUnavailableException;
+
+/**
+ * A {@link GuacamoleUpstreamUnavailableException} whose associated message is
+ * translatable and can be passed through an arbitrary translation service,
+ * producing a human-readable message in the user's native language.
+ */
+public class TranslatableGuacamoleUpstreamUnavailableException extends GuacamoleUpstreamUnavailableException implements Translatable {
+
+    /**
+     * A translatable, human-readable description of the exception that
+     * occurred.
+     */
+    private final TranslatableMessage translatableMessage;
+
+    /**
+     * Creates a new TranslatableGuacamoleUpstreamUnavailableException with the
+     * given message and cause. The message must be provided in both
+     * non-translatable (readable as-written) and translatable forms.
+     *
+     * @param message
+     *     A human-readable description of the exception that occurred. This
+     *     message should be readable on its own and as-written, without
+     *     requiring a translation service.
+     *
+     * @param translatableMessage
+     *     A translatable, human-readable description of the exception that
+     *     occurred.
+     *
+     * @param cause
+     *     The cause of this exception.
+     */
+    public TranslatableGuacamoleUpstreamUnavailableException(String message, TranslatableMessage translatableMessage, Throwable cause) {
+        super(message, cause);
+        this.translatableMessage = translatableMessage;
+    }
+
+    /**
+     * Creates a new TranslatableGuacamoleUpstreamUnavailableException with the
+     * given message. The message must be provided in both non-translatable
+     * (readable as-written) and translatable forms.
+     *
+     * @param message
+     *     A human-readable description of the exception that occurred. This
+     *     message should be readable on its own and as-written, without
+     *     requiring a translation service.
+     *
+     * @param translatableMessage
+     *     A translatable, human-readable description of the exception that
+     *     occurred.
+     */
+    public TranslatableGuacamoleUpstreamUnavailableException(String message, TranslatableMessage translatableMessage) {
+        super(message);
+        this.translatableMessage = translatableMessage;
+    }
+
+    /**
+     * Creates a new TranslatableGuacamoleUpstreamUnavailableException with the
+     * given message and cause. The message must be provided in both
+     * non-translatable (readable as-written) and translatable forms.
+     *
+     * @param message
+     *     A human-readable description of the exception that occurred. This
+     *     message should be readable on its own and as-written, without
+     *     requiring a translation service.
+     *
+     * @param key
+     *     The arbitrary key which can be used to look up the message to be
+     *     displayed in the user's native language.
+     *
+     * @param cause
+     *     The cause of this exception.
+     */
+    public TranslatableGuacamoleUpstreamUnavailableException(String message, String key, Throwable cause) {
+        this(message, new TranslatableMessage(key), cause);
+    }
+
+    /**
+     * Creates a new TranslatableGuacamoleUpstreamUnavailableException with the
+     * given message. The message must be provided in both non-translatable
+     * (readable as-written) and translatable forms.
+     *
+     * @param message
+     *     A human-readable description of the exception that occurred. This
+     *     message should be readable on its own and as-written, without
+     *     requiring a translation service.
+     *
+     * @param key
+     *     The arbitrary key which can be used to look up the message to be
+     *     displayed in the user's native language.
+     */
+    public TranslatableGuacamoleUpstreamUnavailableException(String message, String key) {
+        this(message, new TranslatableMessage(key));
+    }
+
+    @Override
+    public TranslatableMessage getTranslatableMessage() {
+        return translatableMessage;
+    }
+
+}