This is an automated email from the ASF dual-hosted git repository.

hansva pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/hop.git


The following commit(s) were added to refs/heads/main by this push:
     new ece10285d0 added AWS Secrets Manager variable resolver. fixes #8021 
(#8023)
ece10285d0 is described below

commit ece10285d0992ea91671e7b8d8f3d3fd2b922f16
Author: Bart Maertens <[email protected]>
AuthorDate: Thu Aug 20 09:18:57 2026 +0200

    added AWS Secrets Manager variable resolver. fixes #8021 (#8023)
    
    * added AWS Secrets Manager variable resolver. fixes #8021
    
    * key and labels in AWS region combobox. #8021
---
 docs/hop-user-manual/modules/ROOT/nav.adoc         |   1 +
 .../aws-secrets-manager-variable-resolver.adoc     | 249 ++++++++
 .../metadata-types/variable-resolver/index.adoc    |   1 +
 lib/pom.xml                                        |   5 +
 plugins/tech/aws/pom.xml                           |  12 +
 .../resolver/aws/AwsSecretsManagerAuthType.java    |  32 ++
 .../aws/AwsSecretsManagerVariableResolver.java     | 625 +++++++++++++++++++++
 .../aws/messages/messages_de_DE.properties         |  39 ++
 .../aws/messages/messages_en_US.properties         |  39 ++
 .../aws/messages/messages_es_AR.properties         |  39 ++
 .../aws/messages/messages_es_ES.properties         |  39 ++
 .../aws/messages/messages_fr_FR.properties         |  39 ++
 .../aws/messages/messages_it_IT.properties         |  39 ++
 .../aws/messages/messages_ja_JP.properties         |  39 ++
 .../aws/messages/messages_ko_KR.properties         |  39 ++
 .../aws/messages/messages_nl_NL.properties         |  39 ++
 .../aws/messages/messages_no_NO.properties         |  39 ++
 .../aws/messages/messages_pl_PL.properties         |  39 ++
 .../aws/messages/messages_pt_BR.properties         |  39 ++
 .../aws/messages/messages_pt_PT.properties         |  39 ++
 .../aws/messages/messages_zh_CN.properties         |  39 ++
 .../aws/AwsSecretsManagerVariableResolverTest.java | 477 ++++++++++++++++
 .../aws/AwsSecretsManagerWidgetVisibilityTest.java | 177 ++++++
 .../variables/resolver/VariableResolverEditor.java |  64 ++-
 24 files changed, 2173 insertions(+), 16 deletions(-)

diff --git a/docs/hop-user-manual/modules/ROOT/nav.adoc 
b/docs/hop-user-manual/modules/ROOT/nav.adoc
index f4bbb31125..34a5e63868 100644
--- a/docs/hop-user-manual/modules/ROOT/nav.adoc
+++ b/docs/hop-user-manual/modules/ROOT/nav.adoc
@@ -502,6 +502,7 @@ under the License.
 ** xref:metadata-types/jms-connection.adoc[JMS connection]
 ** xref:metadata-types/spark-catalog.adoc[Spark Catalog]
 ** xref:metadata-types/variable-resolver/index.adoc[Variable Resolver]
+*** 
xref:metadata-types/variable-resolver/aws-secrets-manager-variable-resolver.adoc[AWS
 Secrets Manager variable resolver]
 *** 
xref:metadata-types/variable-resolver/azure-key-vault-variable-resolver.adoc[Azure
 Key Vault variable resolver]
 *** 
xref:metadata-types/variable-resolver/google-secret-manager-variable-resolver.adoc[Google
 Secret Manager variable resolver]
 *** 
xref:metadata-types/variable-resolver/hashicorp-vault-variable-resolver.adoc[Hashicorp
 Vault variable resolver]
diff --git 
a/docs/hop-user-manual/modules/ROOT/pages/metadata-types/variable-resolver/aws-secrets-manager-variable-resolver.adoc
 
b/docs/hop-user-manual/modules/ROOT/pages/metadata-types/variable-resolver/aws-secrets-manager-variable-resolver.adoc
new file mode 100644
index 0000000000..b239fbd250
--- /dev/null
+++ 
b/docs/hop-user-manual/modules/ROOT/pages/metadata-types/variable-resolver/aws-secrets-manager-variable-resolver.adoc
@@ -0,0 +1,249 @@
+////
+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.
+////
+[[AwsSecretsManagerVariableResolver]]
+:imagesdir: ../assets/images
+:openvar: #{
+:closevar: }
+:description: The AWS Secrets Manager Variable Resolver looks up secrets in 
AWS Secrets Manager and makes them available as variables in your pipelines and 
workflows.
+
+= AWS Secrets Manager Variable Resolver
+
+== Description
+
+The AWS Secrets Manager Variable Resolver looks up secrets in AWS Secrets 
Manager and makes them available as variables in your Apache Hop pipelines and 
workflows.
+Database passwords, API keys and other credentials stay in AWS and out of your 
project files.
+
+Unlike Azure Key Vault or Hashicorp Vault there is nothing to install or 
provision: Secrets Manager is available in every region of every AWS account.
+You only need a secret and an identity that is allowed to read it.
+
+== Options
+
+The editor only shows the options that apply to the authentication type you 
picked, so switching type changes which fields are on screen.
+Every option accepts variables, which is what lets one resolver serve several 
environments.
+
+=== AWS region
+
+The region that holds your secrets.
+The dropdown lists each region with the name AWS gives it, for example 
`eu-west-1 - Europe (Ireland)`, but only the region code is stored and used, so 
the metadata file simply reads `eu-west-1`.
+You can also type a code yourself, or set a variable such as `${AWS_REGION}`.
+
+Secrets are regional and are not replicated between regions unless you 
explicitly configure replication, so this has to match the region the secret 
was created in.
+Leave the option empty to let Hop pick up the region the way the AWS CLI does, 
from the `AWS_REGION` environment variable or the active profile.
+
+=== Authentication type
+
+How Hop proves to AWS who it is. The three types have no options in common, so 
the editor shows only the fields belonging to the one you pick.
+
+`AUTOMATIC`:: Let the AWS SDK find credentials by itself. This is the default, 
and the one to prefer.
++
+The SDK works through its standard provider chain and takes the first 
credentials it finds: Java system properties, then environment variables 
(`AWS_ACCESS_KEY_ID` and `AWS_SECRET_ACCESS_KEY`), then a web identity token 
(how EKS IRSA works), then the shared credentials file, then container 
credentials (the ECS task role), and finally the EC2 instance profile.
++
+On EC2, ECS or EKS this means the surrounding infrastructure supplies a role 
automatically and no credential is stored in the Hop project at all. It also 
picks up whatever you already use locally, so if `aws sts get-caller-identity` 
works in the shell you start Hop from, this option works too.
++
+Needs no further options.
+
+`ACCESS_KEYS`:: Use an access key and secret key you enter yourself, with an 
optional session token.
++
+Use this when Hop runs outside AWS and there is no profile to read, or when 
you deliberately want a separate identity for secret lookups. Uses the *Access 
key*, *Secret key* and *Session token* options.
+
+`CREDENTIALS_FILE`:: Read credentials from an AWS credentials file, optionally 
choosing a profile from it.
++
+Use this when you already keep several AWS identities side by side and want 
this resolver to use a specific one rather than whichever the chain happens to 
find first. Uses the *Credentials file* and *Profile name* options.
+
+[TIP]
+====
+Prefer `AUTOMATIC` wherever the runtime environment can provide credentials.
+Long lived access keys stored in a project are exactly the kind of thing 
Secrets Manager exists to get rid of, and `AUTOMATIC` keeps the Hop project 
identical between a laptop, a server and a container.
+====
+
+=== Access key / Secret key / Session token
+
+The AWS access key ID and secret access key, used only with authentication 
type `ACCESS_KEYS`.
+
+The session token is only needed for temporary credentials, the kind handed 
out by `sts assume-role` or by AWS SSO. Leave it empty for a normal long lived 
key pair. Temporary credentials expire, so a resolver configured with them 
stops working when they do.
+
+All three values are stored encrypted in the metadata file.
+
+=== Credentials file / Profile name
+
+Used with authentication type `CREDENTIALS_FILE`.
+
+The credentials file is a file in the format the AWS CLI writes, holding one 
or more named profiles.
+Leave it empty to use the standard location, `~/.aws/credentials` 
(`%USERPROFILE%\.aws\credentials` on Windows).
+
+The profile name picks which entry in that file to use, and defaults to 
`default`.
+
+=== Endpoint override
+
+An alternative Secrets Manager endpoint.
+Use this for a VPC endpoint, or to point Hop at a local AWS emulator during 
development.
+Leave it empty to use the regular regional AWS endpoint.
+
+=== Secret name prefix
+
+A prefix placed in front of every secret name before the lookup.
+With a prefix of `production/`, the expression 
`{openvar}aws-secrets:database{closevar}` reads the secret 
`production/database`.
+
+This keeps expressions short and lets the same pipeline read from 
`development/` or `production/` by pointing the resolver at a different prefix 
per environment.
+The prefix itself may contain variables, for example `${ENVIRONMENT}/`.
+
+=== Version stage
+
+Which version of the secret to read. `AWSCURRENT` by default, which is what 
you want unless you have a specific reason not to.
+
+Secrets Manager does not overwrite a secret when its value changes: it stores 
a new *version* and keeps the old ones. A *staging label* is a moving pointer 
to one of those versions, so that something reading "the current value" does 
not need to know any version IDs. AWS maintains three labels for you:
+
+`AWSCURRENT`:: The value in use right now. This is what an application should 
read, and what this resolver reads unless you say otherwise.
+`AWSPREVIOUS`:: The value that was current before the most recent change.
+`AWSPENDING`:: A new value created by a rotation that has not been promoted 
yet. It exists only while a rotation is in progress.
+
+When a secret rotates, AWS creates the new version with `AWSPENDING`, tests 
it, and then moves `AWSCURRENT` onto it while the version it replaced picks up 
`AWSPREVIOUS`. Because the labels move rather than the values, an expression 
reading `AWSCURRENT` keeps working across a rotation without any change in Hop.
+
+Reasons to change this option are rare and usually temporary:
+
+* A rotation broke something and you need the previous credential back while 
you sort it out: `AWSPREVIOUS`.
+* You are testing a rotation before it is promoted: `AWSPENDING`.
+
+[WARNING]
+====
+Do not leave the option on `AWSPREVIOUS`. It is a pointer, not a snapshot: the 
next rotation moves it to what is current today, so a resolver left pointing at 
it silently starts serving a different secret.
+====
+
+=== Cache TTL (seconds)
+
+How long a retrieved secret is reused before Hop asks AWS for it again.
+`0`, the default, disables caching and looks the secret up every time.
+
+Secrets Manager bills per API call and Hop resolves variables often, so a 
small value such as `60` can noticeably reduce both cost and latency.
+The trade-off is that a rotated secret takes up to this long to be picked up, 
so keep the value well below your rotation window.
+
+== Expression syntax
+
+Secrets are addressed with the standard variable resolver syntax:
+
+`{openvar}name:secret-name:value-key{closevar}`
+
+* `name`: the name of the variable resolver metadata element
+* `secret-name`: the name of the secret in AWS Secrets Manager
+* `value-key`: when the secret holds JSON, the key to pick out of it (OPTIONAL)
+
+A secret can hold a plain value or JSON: `SecretString` is free-form text and 
AWS makes no distinction.
+The console nudges you towards JSON because its create-secret screen opens on 
a *Key/value* tab, but the *Plaintext* tab next to it stores a bare value just 
as well.
+
+For a plain secret, two parts are enough:
+
+* `{openvar}aws-secrets:my-api-key{closevar}` : the value as stored
+
+For a JSON secret, the third part picks one value out of it.
+Given a secret called `some-db` holding:
+
+[source,json]
+----
+{"dbname":"test","host":"localhost","password":"some-password","port":3306,"username":"john"}
+----
+
+a resolver named `aws-secrets` gives you:
+
+* `{openvar}aws-secrets:some-db:host{closevar}` : localhost
+* `{openvar}aws-secrets:some-db:username{closevar}` : john
+* `{openvar}aws-secrets:some-db{closevar}` : the entire JSON document
+
+=== Keys in database credential secrets
+
+Secrets created with one of the console's database types (RDS, DocumentDB, 
Redshift) always use the same fixed key names, because the AWS rotation 
functions expect them:
+
+`username`, `password`, `engine`, `host`, `port`, `dbname`, 
`dbInstanceIdentifier`
+
+Note `host`, not `hostname`.
+Asking for a key that is not in the secret yields an empty value rather than 
an error, so if a lookup comes back empty, resolve the secret without the third 
part first and check the key names in the JSON you get back.
+
+[IMPORTANT]
+====
+Use the *name* of the secret, not its ARN.
+Resolver expressions are split on the `:` character and an ARN contains colons 
of its own, so `{openvar}aws-secrets:arn:aws:secretsmanager:...{closevar}` 
cannot work.
+When your secret names share a common path, put that path in the *Secret name 
prefix* option instead of typing it in every expression.
+====
+
+== Setting up AWS
+
+=== Create a secret
+
+[source,bash]
+----
+# A plain value, resolved with a two part expression
+aws secretsmanager create-secret \
+  --name production/api-key \
+  --secret-string "my-api-key"
+
+# A JSON secret, so a single value can be picked out of it
+aws secretsmanager create-secret \
+  --name production/some-db \
+  --secret-string 
'{"hostname":"localhost","port":"3306","username":"john","password":"some-password"}'
+----
+
+=== Grant read access
+
+The identity Hop runs as needs `secretsmanager:GetSecretValue` on the secrets 
it reads:
+
+[source,json]
+----
+{
+  "Version": "2012-10-17",
+  "Statement": [
+    {
+      "Effect": "Allow",
+      "Action": "secretsmanager:GetSecretValue",
+      "Resource": 
"arn:aws:secretsmanager:eu-west-1:123456789012:secret:production/*"
+    }
+  ]
+}
+----
+
+Secrets encrypted with a customer managed KMS key also need `kms:Decrypt` on 
that key.
+Secrets using the AWS managed `aws/secretsmanager` key need no extra 
permission.
+
+== Usage
+
+=== In a database connection
+
+1. Create or edit a relational database connection
+2. Set the *Password* field to 
`{openvar}aws-secrets:production/some-db:password{closevar}`
+3. The password is retrieved from Secrets Manager when the connection is used
+
+=== Per environment
+
+Set a variable in each of your 
xref:projects/projects-environments.adoc[environments] and let it point at the 
resolver expression in production only:
+
+* development: `PASSWORD` = `dev-password`
+* production: `PASSWORD` = 
`{openvar}aws-secrets:production/some-db:password{closevar}`
+
+Pipelines and workflows simply use `${PASSWORD}` and need no changes between 
environments.
+
+== Troubleshooting
+
+Errors are not shown in the user interface while you type an expression, since 
that would make expressions impossible to enter.
+Check the log in the xref:hop-gui/index.adoc[Hop GUI] execution pane to see 
what the resolver reports.
+
+Common causes:
+
+* *An empty value came back*: the lookup worked but the JSON key does not 
exist. Resolve the secret without the third part to see the real key names.
+* *Secret not found*: the secret does not exist in the configured region, or 
the name prefix is missing. Remember that secrets are regional.
+* *An ARN was used instead of a name*: see the note in the expression syntax 
section above.
+* *Access denied*: the identity is missing `secretsmanager:GetSecretValue`, or 
`kms:Decrypt` for a customer managed key.
+* *No region*: no region is configured on the resolver and none could be found 
in the environment.
+
+NOTE: A secret holding a binary value is returned base64 encoded, since a 
variable value has to be text.
diff --git 
a/docs/hop-user-manual/modules/ROOT/pages/metadata-types/variable-resolver/index.adoc
 
b/docs/hop-user-manual/modules/ROOT/pages/metadata-types/variable-resolver/index.adoc
index 8a529cd2fd..9de1b4a8fd 100644
--- 
a/docs/hop-user-manual/modules/ROOT/pages/metadata-types/variable-resolver/index.adoc
+++ 
b/docs/hop-user-manual/modules/ROOT/pages/metadata-types/variable-resolver/index.adoc
@@ -56,6 +56,7 @@ NOTE: For now, the variable resolver is not doing any more 
nested resolving to a
 
 Here are the available variable resolver plugins:
 
+* 
xref:metadata-types/variable-resolver/aws-secrets-manager-variable-resolver.adoc[AWS
 Secrets Manager variable resolver]
 * 
xref:metadata-types/variable-resolver/azure-key-vault-variable-resolver.adoc[Azure
 Key Vault variable resolver]
 * 
xref:metadata-types/variable-resolver/google-secret-manager-variable-resolver.adoc[Google
 Secret Manager Variable Resolver]
 * 
xref:metadata-types/variable-resolver/hashicorp-vault-variable-resolver.adoc[Hashicorp
 Vault variable resolver]
diff --git a/lib/pom.xml b/lib/pom.xml
index 91cee4ff11..d0014185c1 100644
--- a/lib/pom.xml
+++ b/lib/pom.xml
@@ -254,6 +254,11 @@
                 <artifactId>s3</artifactId>
                 <version>${aws-java-sdk.version}</version>
             </dependency>
+            <dependency>
+                <groupId>software.amazon.awssdk</groupId>
+                <artifactId>secretsmanager</artifactId>
+                <version>${aws-java-sdk.version}</version>
+            </dependency>
             <dependency>
                 <groupId>software.amazon.awssdk</groupId>
                 <artifactId>sns</artifactId>
diff --git a/plugins/tech/aws/pom.xml b/plugins/tech/aws/pom.xml
index f471f7eb25..b5465a68d5 100644
--- a/plugins/tech/aws/pom.xml
+++ b/plugins/tech/aws/pom.xml
@@ -76,6 +76,18 @@
                 </exclusion>
             </exclusions>
         </dependency>
+        <dependency>
+            <groupId>software.amazon.awssdk</groupId>
+            <artifactId>secretsmanager</artifactId>
+            <version>${aws-java-sdk.version}</version>
+            <scope>provided</scope>
+            <exclusions>
+                <exclusion>
+                    <groupId>io.netty</groupId>
+                    <artifactId>*</artifactId>
+                </exclusion>
+            </exclusions>
+        </dependency>
         <dependency>
             <groupId>software.amazon.awssdk</groupId>
             <artifactId>sns</artifactId>
diff --git 
a/plugins/tech/aws/src/main/java/org/apache/hop/core/variables/resolver/aws/AwsSecretsManagerAuthType.java
 
b/plugins/tech/aws/src/main/java/org/apache/hop/core/variables/resolver/aws/AwsSecretsManagerAuthType.java
new file mode 100644
index 0000000000..c7f91ec10a
--- /dev/null
+++ 
b/plugins/tech/aws/src/main/java/org/apache/hop/core/variables/resolver/aws/AwsSecretsManagerAuthType.java
@@ -0,0 +1,32 @@
+/*
+ * 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.hop.core.variables.resolver.aws;
+
+/** Authentication method used by the AWS Secrets Manager variable resolver. */
+public enum AwsSecretsManagerAuthType {
+  /**
+   * Let the AWS SDK find credentials by itself, through the default provider 
chain: system
+   * properties, environment variables, a web identity token, the credentials 
file, container
+   * credentials and finally the EC2 instance profile.
+   */
+  AUTOMATIC,
+  /** An explicit access key and secret key, optionally with a session token. 
*/
+  ACCESS_KEYS,
+  /** An AWS credentials file, optionally with a profile name. */
+  CREDENTIALS_FILE,
+}
diff --git 
a/plugins/tech/aws/src/main/java/org/apache/hop/core/variables/resolver/aws/AwsSecretsManagerVariableResolver.java
 
b/plugins/tech/aws/src/main/java/org/apache/hop/core/variables/resolver/aws/AwsSecretsManagerVariableResolver.java
new file mode 100644
index 0000000000..8eef7a96b3
--- /dev/null
+++ 
b/plugins/tech/aws/src/main/java/org/apache/hop/core/variables/resolver/aws/AwsSecretsManagerVariableResolver.java
@@ -0,0 +1,625 @@
+/*
+ * 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.hop.core.variables.resolver.aws;
+
+import java.net.URI;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.Base64;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.stream.Collectors;
+import lombok.Getter;
+import lombok.Setter;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.hop.core.Const;
+import org.apache.hop.core.encryption.Encr;
+import org.apache.hop.core.exception.HopException;
+import org.apache.hop.core.gui.plugin.GuiElementType;
+import org.apache.hop.core.gui.plugin.GuiPlugin;
+import org.apache.hop.core.gui.plugin.GuiWidgetElement;
+import org.apache.hop.core.logging.ILogChannel;
+import org.apache.hop.core.logging.LogChannel;
+import org.apache.hop.core.variables.IVariables;
+import org.apache.hop.core.variables.resolver.IVariableResolver;
+import org.apache.hop.core.variables.resolver.VariableResolver;
+import org.apache.hop.core.variables.resolver.VariableResolverPlugin;
+import org.apache.hop.metadata.api.HopMetadataProperty;
+import org.apache.hop.metadata.api.IHopMetadataProvider;
+import org.apache.hop.ui.core.gui.GuiCompositeWidgets;
+import org.apache.hop.ui.core.gui.IGuiPluginCompositeWidgetsListener;
+import org.eclipse.swt.widgets.Combo;
+import org.eclipse.swt.widgets.Control;
+import software.amazon.awssdk.auth.credentials.AwsBasicCredentials;
+import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider;
+import software.amazon.awssdk.auth.credentials.AwsSessionCredentials;
+import software.amazon.awssdk.auth.credentials.DefaultCredentialsProvider;
+import software.amazon.awssdk.auth.credentials.ProfileCredentialsProvider;
+import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider;
+import software.amazon.awssdk.profiles.ProfileFile;
+import software.amazon.awssdk.regions.Region;
+import software.amazon.awssdk.regions.RegionMetadata;
+import software.amazon.awssdk.regions.providers.DefaultAwsRegionProviderChain;
+import software.amazon.awssdk.services.secretsmanager.SecretsManagerClient;
+import 
software.amazon.awssdk.services.secretsmanager.SecretsManagerClientBuilder;
+import 
software.amazon.awssdk.services.secretsmanager.model.GetSecretValueRequest;
+import 
software.amazon.awssdk.services.secretsmanager.model.GetSecretValueResponse;
+import 
software.amazon.awssdk.services.secretsmanager.model.ResourceNotFoundException;
+
+/**
+ * Looks up secrets in AWS Secrets Manager.
+ *
+ * <p>A secret is addressed by its name, not by its ARN: the variable resolver 
expression syntax
+ * <code>#{name:secret:key}</code> splits on the colon, and an ARN contains 
colons of its own. Use
+ * the secret name prefix option to reach secrets that need a longer path.
+ *
+ * <p>Secrets Manager secrets frequently hold JSON. Picking a single value out 
of that JSON is
+ * handled generically by the variable resolver machinery, so this resolver 
simply returns the
+ * secret payload as it is stored.
+ */
+@Getter
+@Setter
+@GuiPlugin
+@VariableResolverPlugin(
+    id = "AwsSecretsManager",
+    name = "AWS Secrets Manager Variable Resolver",
+    description = "Look up values of secrets in AWS Secrets Manager",
+    documentationUrl =
+        
"/metadata-types/variable-resolver/aws-secrets-manager-variable-resolver.html")
+public class AwsSecretsManagerVariableResolver
+    implements IVariableResolver, IGuiPluginCompositeWidgetsListener {
+
+  private static final String DEFAULT_VERSION_STAGE = "AWSCURRENT";
+
+  /** Separates the region code from its description in the region combo box. 
*/
+  private static final String REGION_DESCRIPTION_SEPARATOR = " - ";
+
+  /** The region codes the SDK knows, so that anything else can be left alone. 
*/
+  private static final Set<String> KNOWN_REGION_IDS =
+      
Region.regions().stream().map(Region::id).collect(Collectors.toUnmodifiableSet());
+
+  static final String ID_REGION = "region";
+  static final String ID_AUTHENTICATION_TYPE = "authenticationType";
+  static final String ID_ACCESS_KEY = "accessKey";
+  static final String ID_SECRET_KEY = "secretKey";
+  static final String ID_SESSION_TOKEN = "sessionToken";
+  static final String ID_CREDENTIALS_FILE = "credentialsFile";
+  static final String ID_PROFILE_NAME = "profileName";
+  static final String ID_ENDPOINT_OVERRIDE = "endpointOverride";
+  static final String ID_SECRET_NAME_PREFIX = "secretNamePrefix";
+  static final String ID_VERSION_STAGE = "versionStage";
+  static final String ID_CACHE_TTL_SECONDS = "cacheTtlSeconds";
+
+  private final LogChannel log = new 
LogChannel("AwsSecretsManagerVariableResolver");
+
+  /** Guards {@link #client}, {@link #clientSignature} and {@link #cache}. */
+  private final Object clientLock = new Object();
+
+  private transient SecretsManagerClient client;
+
+  /** The resolved configuration the cached client was built for. */
+  private transient String clientSignature;
+
+  private final transient Map<String, CachedSecret> cache = new 
ConcurrentHashMap<>();
+
+  @GuiWidgetElement(
+      id = ID_REGION,
+      order = "010",
+      parentId = VariableResolver.GUI_PLUGIN_ELEMENT_PARENT_ID,
+      type = GuiElementType.COMBO,
+      comboValuesMethod = "getRegions",
+      getterMethod = "getRegionLabel",
+      label = "i18n::AwsSecretsManagerVariableResolver.Region.Label",
+      toolTip = "i18n::AwsSecretsManagerVariableResolver.Region.Tooltip")
+  @HopMetadataProperty
+  private String region;
+
+  @GuiWidgetElement(
+      id = ID_AUTHENTICATION_TYPE,
+      order = "020",
+      parentId = VariableResolver.GUI_PLUGIN_ELEMENT_PARENT_ID,
+      type = GuiElementType.COMBO,
+      comboValuesMethod = "getAuthenticationTypes",
+      label = 
"i18n::AwsSecretsManagerVariableResolver.AuthenticationType.Label",
+      toolTip = 
"i18n::AwsSecretsManagerVariableResolver.AuthenticationType.Tooltip")
+  @HopMetadataProperty
+  private String authenticationType;
+
+  @GuiWidgetElement(
+      id = ID_ACCESS_KEY,
+      order = "030",
+      parentId = VariableResolver.GUI_PLUGIN_ELEMENT_PARENT_ID,
+      type = GuiElementType.TEXT,
+      password = true,
+      label = "i18n::AwsSecretsManagerVariableResolver.AccessKey.Label",
+      toolTip = "i18n::AwsSecretsManagerVariableResolver.AccessKey.Tooltip")
+  @HopMetadataProperty(password = true)
+  private String accessKey;
+
+  @GuiWidgetElement(
+      id = ID_SECRET_KEY,
+      order = "040",
+      parentId = VariableResolver.GUI_PLUGIN_ELEMENT_PARENT_ID,
+      type = GuiElementType.TEXT,
+      password = true,
+      label = "i18n::AwsSecretsManagerVariableResolver.SecretKey.Label",
+      toolTip = "i18n::AwsSecretsManagerVariableResolver.SecretKey.Tooltip")
+  @HopMetadataProperty(password = true)
+  private String secretKey;
+
+  @GuiWidgetElement(
+      id = ID_SESSION_TOKEN,
+      order = "050",
+      parentId = VariableResolver.GUI_PLUGIN_ELEMENT_PARENT_ID,
+      type = GuiElementType.TEXT,
+      password = true,
+      label = "i18n::AwsSecretsManagerVariableResolver.SessionToken.Label",
+      toolTip = "i18n::AwsSecretsManagerVariableResolver.SessionToken.Tooltip")
+  @HopMetadataProperty(password = true)
+  private String sessionToken;
+
+  @GuiWidgetElement(
+      id = ID_CREDENTIALS_FILE,
+      order = "060",
+      parentId = VariableResolver.GUI_PLUGIN_ELEMENT_PARENT_ID,
+      type = GuiElementType.FILENAME,
+      label = "i18n::AwsSecretsManagerVariableResolver.CredentialsFile.Label",
+      toolTip = 
"i18n::AwsSecretsManagerVariableResolver.CredentialsFile.Tooltip")
+  @HopMetadataProperty
+  private String credentialsFile;
+
+  @GuiWidgetElement(
+      id = ID_PROFILE_NAME,
+      order = "070",
+      parentId = VariableResolver.GUI_PLUGIN_ELEMENT_PARENT_ID,
+      type = GuiElementType.TEXT,
+      label = "i18n::AwsSecretsManagerVariableResolver.ProfileName.Label",
+      toolTip = "i18n::AwsSecretsManagerVariableResolver.ProfileName.Tooltip")
+  @HopMetadataProperty
+  private String profileName;
+
+  @GuiWidgetElement(
+      id = ID_ENDPOINT_OVERRIDE,
+      order = "080",
+      parentId = VariableResolver.GUI_PLUGIN_ELEMENT_PARENT_ID,
+      type = GuiElementType.TEXT,
+      label = "i18n::AwsSecretsManagerVariableResolver.EndpointOverride.Label",
+      toolTip = 
"i18n::AwsSecretsManagerVariableResolver.EndpointOverride.Tooltip")
+  @HopMetadataProperty
+  private String endpointOverride;
+
+  @GuiWidgetElement(
+      id = ID_SECRET_NAME_PREFIX,
+      order = "090",
+      parentId = VariableResolver.GUI_PLUGIN_ELEMENT_PARENT_ID,
+      type = GuiElementType.TEXT,
+      label = "i18n::AwsSecretsManagerVariableResolver.SecretNamePrefix.Label",
+      toolTip = 
"i18n::AwsSecretsManagerVariableResolver.SecretNamePrefix.Tooltip")
+  @HopMetadataProperty
+  private String secretNamePrefix;
+
+  @GuiWidgetElement(
+      id = ID_VERSION_STAGE,
+      order = "100",
+      parentId = VariableResolver.GUI_PLUGIN_ELEMENT_PARENT_ID,
+      type = GuiElementType.TEXT,
+      label = "i18n::AwsSecretsManagerVariableResolver.VersionStage.Label",
+      toolTip = "i18n::AwsSecretsManagerVariableResolver.VersionStage.Tooltip")
+  @HopMetadataProperty
+  private String versionStage;
+
+  @GuiWidgetElement(
+      id = ID_CACHE_TTL_SECONDS,
+      order = "110",
+      parentId = VariableResolver.GUI_PLUGIN_ELEMENT_PARENT_ID,
+      type = GuiElementType.TEXT,
+      label = "i18n::AwsSecretsManagerVariableResolver.CacheTtlSeconds.Label",
+      toolTip = 
"i18n::AwsSecretsManagerVariableResolver.CacheTtlSeconds.Tooltip")
+  @HopMetadataProperty
+  private String cacheTtlSeconds;
+
+  /**
+   * The region combo shows "eu-west-1 - Europe (Ireland)" but only the code 
is worth keeping, so
+   * the description is stripped before it reaches the field and the metadata 
file. Hand written on
+   * purpose: this is the setter the widget layer calls, and Lombok leaves it 
alone.
+   */
+  public void setRegion(String region) {
+    this.region = regionCode(region);
+  }
+
+  /**
+   * The value to show in the region combo box: the stored code together with 
its description, so
+   * that it matches one of the entries {@link #getRegions} offers. Anything 
that is not a region
+   * code -- a variable reference, most likely -- is shown as it is.
+   */
+  public String getRegionLabel() {
+    String code = regionCode(region);
+    if (StringUtils.isEmpty(code) || !KNOWN_REGION_IDS.contains(code)) {
+      return region;
+    }
+    return code + REGION_DESCRIPTION_SEPARATOR + describeRegion(code);
+  }
+
+  public AwsSecretsManagerVariableResolver() {
+    authenticationType = AwsSecretsManagerAuthType.AUTOMATIC.name();
+    versionStage = DEFAULT_VERSION_STAGE;
+    cacheTtlSeconds = "0";
+  }
+
+  @Override
+  public void init() {
+    // The client is built lazily on the first resolve() call: only then do we 
have the variables
+    // needed to resolve the configuration fields.
+  }
+
+  @Override
+  public String resolve(String secretName, IVariables variables) throws 
HopException {
+    if (StringUtils.isEmpty(secretName)) {
+      return null;
+    }
+
+    // An ARN never reaches us intact: the expression is split on ':' before 
this method is called,
+    // so #{name:arn:aws:secretsmanager:...} arrives here as the bare string 
"arn". Refusing it is
+    // friendlier than looking up a secret that was never meant.
+    if ("arn".equals(secretName) || secretName.startsWith("arn:")) {
+      log.logError(
+          "Secret '"
+              + secretName
+              + "' looks like an ARN. Variable resolver expressions are split 
on ':', so an ARN "
+              + "cannot be used here. Use the name of the secret and, when the 
names share a "
+              + "common path, the secret name prefix option of this 
resolver.");
+      return null;
+    }
+
+    String prefix = variables.resolve(secretNamePrefix);
+    String secretId = StringUtils.isEmpty(prefix) ? secretName : prefix + 
secretName;
+
+    long ttl = Const.toLong(variables.resolve(cacheTtlSeconds), 0L);
+    if (ttl > 0) {
+      CachedSecret cached = cache.get(secretId);
+      if (cached != null && !cached.isExpired()) {
+        return cached.value;
+      }
+    }
+
+    try {
+      SecretsManagerClient secretsManagerClient = getClient(variables);
+
+      String stage = variables.resolve(versionStage);
+      GetSecretValueRequest request =
+          GetSecretValueRequest.builder()
+              .secretId(secretId)
+              .versionStage(StringUtils.isEmpty(stage) ? DEFAULT_VERSION_STAGE 
: stage)
+              .build();
+
+      GetSecretValueResponse response = 
secretsManagerClient.getSecretValue(request);
+      String value = extractValue(secretId, response);
+
+      if (value != null && ttl > 0) {
+        cache.put(secretId, new CachedSecret(value, System.currentTimeMillis() 
+ ttl * 1000L));
+      }
+      return value;
+    } catch (ResourceNotFoundException e) {
+      log.logError("Secret '" + secretId + "' was not found in AWS Secrets 
Manager", e);
+      return null;
+    } catch (Exception e) {
+      log.logError("Error looking up secret '" + secretId + "' in AWS Secrets 
Manager", e);
+      return null;
+    }
+  }
+
+  /**
+   * A Secrets Manager secret holds either a string or a binary payload. 
Binary secrets are returned
+   * base64 encoded since a variable value has to be a String.
+   */
+  private String extractValue(String secretId, GetSecretValueResponse 
response) {
+    if (response.secretString() != null) {
+      return response.secretString();
+    }
+    if (response.secretBinary() != null) {
+      log.logDetailed(
+          "Secret '" + secretId + "' holds a binary value, returning it base64 
encoded.");
+      return 
Base64.getEncoder().encodeToString(response.secretBinary().asByteArray());
+    }
+    log.logError("Secret '" + secretId + "' holds neither a string nor a 
binary value");
+    return null;
+  }
+
+  /**
+   * Secrets Manager bills per API call and variable resolution happens often, 
so the client is
+   * built once and kept. It is rebuilt when the resolved configuration 
changes, which can happen
+   * when the configuration fields themselves contain variables.
+   */
+  protected SecretsManagerClient getClient(IVariables variables) throws 
HopException {
+    String actualRegion = variables.resolve(region);
+    String actualAuthType = variables.resolve(authenticationType);
+    String actualAccessKey = variables.resolve(accessKey);
+    String actualSecretKey = variables.resolve(secretKey);
+    String actualSessionToken = variables.resolve(sessionToken);
+    String actualCredentialsFile = variables.resolve(credentialsFile);
+    String actualProfileName = variables.resolve(profileName);
+    String actualEndpoint = variables.resolve(endpointOverride);
+
+    String signature =
+        String.join(
+            "\t",
+            Const.NVL(actualRegion, ""),
+            Const.NVL(actualAuthType, ""),
+            Const.NVL(actualAccessKey, ""),
+            Const.NVL(actualSecretKey, ""),
+            Const.NVL(actualSessionToken, ""),
+            Const.NVL(actualCredentialsFile, ""),
+            Const.NVL(actualProfileName, ""),
+            Const.NVL(actualEndpoint, ""));
+
+    synchronized (clientLock) {
+      if (client != null && signature.equals(clientSignature)) {
+        return client;
+      }
+
+      if (client != null) {
+        // The configuration changed, so anything we cached for it is stale 
too.
+        client.close();
+        cache.clear();
+      }
+
+      SecretsManagerClientBuilder builder = SecretsManagerClient.builder();
+      builder.region(resolveRegion(actualRegion));
+      builder.credentialsProvider(
+          buildCredentialsProvider(
+              actualAuthType,
+              actualAccessKey,
+              actualSecretKey,
+              actualSessionToken,
+              actualCredentialsFile,
+              actualProfileName));
+      if (StringUtils.isNotEmpty(actualEndpoint)) {
+        builder.endpointOverride(URI.create(actualEndpoint));
+      }
+
+      client = builder.build();
+      clientSignature = signature;
+      return client;
+    }
+  }
+
+  /**
+   * The AWS SDK needs a region to build a client. When none is configured we 
let the SDK look one
+   * up the way the AWS CLI does, through AWS_REGION or the active profile.
+   */
+  private Region resolveRegion(String actualRegion) throws HopException {
+    String code = regionCode(actualRegion);
+    if (StringUtils.isNotEmpty(code)) {
+      return Region.of(code);
+    }
+    try {
+      return new DefaultAwsRegionProviderChain().getRegion();
+    } catch (Exception e) {
+      throw new HopException(
+          "No AWS region is configured in this variable resolver and none 
could be found in the "
+              + "environment. Set the region option or the AWS_REGION 
environment variable.",
+          e);
+    }
+  }
+
+  /**
+   * The region combo box shows the code together with the description AWS 
gives it, so a stored
+   * value can read "eu-west-1 - Europe (Ireland)". A region code never 
contains whitespace, which
+   * makes the first word the code whichever of the two forms was saved.
+   */
+  static String regionCode(String region) {
+    if (StringUtils.isEmpty(region)) {
+      return region;
+    }
+    int index = region.indexOf(REGION_DESCRIPTION_SEPARATOR);
+    return index < 0 ? region.trim() : region.substring(0, index).trim();
+  }
+
+  AwsCredentialsProvider buildCredentialsProvider(
+      String actualAuthType,
+      String actualAccessKey,
+      String actualSecretKey,
+      String actualSessionToken,
+      String actualCredentialsFile,
+      String actualProfileName)
+      throws HopException {
+
+    AwsSecretsManagerAuthType authType = parseAuthType(actualAuthType);
+
+    switch (authType) {
+      case ACCESS_KEYS:
+        if (StringUtils.isEmpty(actualAccessKey) || 
StringUtils.isEmpty(actualSecretKey)) {
+          throw new HopException(
+              "An access key and a secret key are required when the 
authentication type is "
+                  + AwsSecretsManagerAuthType.ACCESS_KEYS.name());
+        }
+        String decryptedSecretKey = 
Encr.decryptPasswordOptionallyEncrypted(actualSecretKey);
+        if (StringUtils.isEmpty(actualSessionToken)) {
+          return StaticCredentialsProvider.create(
+              AwsBasicCredentials.create(actualAccessKey, decryptedSecretKey));
+        }
+        return StaticCredentialsProvider.create(
+            AwsSessionCredentials.create(
+                actualAccessKey,
+                decryptedSecretKey,
+                Encr.decryptPasswordOptionallyEncrypted(actualSessionToken)));
+
+      case CREDENTIALS_FILE:
+        ProfileCredentialsProvider.Builder profileBuilder = 
ProfileCredentialsProvider.builder();
+        if (StringUtils.isNotEmpty(actualCredentialsFile)) {
+          profileBuilder.profileFile(
+              ProfileFile.builder()
+                  .content(Paths.get(actualCredentialsFile))
+                  .type(ProfileFile.Type.CREDENTIALS)
+                  .build());
+        }
+        profileBuilder.profileName(
+            StringUtils.isEmpty(actualProfileName) ? "default" : 
actualProfileName);
+        return profileBuilder.build();
+
+      default:
+        return DefaultCredentialsProvider.create();
+    }
+  }
+
+  AwsSecretsManagerAuthType parseAuthType(String actualAuthType) throws 
HopException {
+    if (StringUtils.isEmpty(actualAuthType)) {
+      return AwsSecretsManagerAuthType.AUTOMATIC;
+    }
+    try {
+      return 
AwsSecretsManagerAuthType.valueOf(actualAuthType.trim().toUpperCase());
+    } catch (IllegalArgumentException e) {
+      throw new HopException(
+          "Unknown AWS authentication type '"
+              + actualAuthType
+              + "'. Valid values are: "
+              + String.join(", ", authTypeNames()),
+          e);
+    }
+  }
+
+  private static List<String> authTypeNames() {
+    List<String> names = new ArrayList<>();
+    for (AwsSecretsManagerAuthType type : AwsSecretsManagerAuthType.values()) {
+      names.add(type.name());
+    }
+    return names;
+  }
+
+  /**
+   * Populates the region combo box from the regions the AWS SDK knows about, 
each with the
+   * description AWS gives it, so that "eu-west-1" reads as "eu-west-1 - 
Europe (Ireland)".
+   */
+  public List<String> getRegions(ILogChannel logChannel, IHopMetadataProvider 
metadataProvider) {
+    List<String> regions = new ArrayList<>();
+    // An empty entry leaves the region to the environment.
+    regions.add("");
+    Region.regions().stream()
+        .map(Region::id)
+        .sorted()
+        .forEach(id -> regions.add(id + REGION_DESCRIPTION_SEPARATOR + 
describeRegion(id)));
+    return regions;
+  }
+
+  private String describeRegion(String id) {
+    try {
+      return RegionMetadata.of(Region.of(id)).description();
+    } catch (Exception e) {
+      // A region the SDK knows by id but carries no description for.
+      return id;
+    }
+  }
+
+  /** Populates the authentication type combo box. */
+  public List<String> getAuthenticationTypes(
+      ILogChannel logChannel, IHopMetadataProvider metadataProvider) {
+    return authTypeNames();
+  }
+
+  @Override
+  public void widgetsCreated(GuiCompositeWidgets compositeWidgets) {
+    hideFieldsThatDoNotApply(compositeWidgets);
+  }
+
+  @Override
+  public void widgetsPopulated(GuiCompositeWidgets compositeWidgets) {
+    hideFieldsThatDoNotApply(compositeWidgets);
+  }
+
+  @Override
+  public void widgetModified(
+      GuiCompositeWidgets compositeWidgets, Control changedWidget, String 
widgetId) {
+    if (ID_AUTHENTICATION_TYPE.equals(widgetId)) {
+      hideFieldsThatDoNotApply(compositeWidgets);
+    }
+  }
+
+  @Override
+  public void persistContents(GuiCompositeWidgets compositeWidgets) {
+    // Not needed, the editor reads the widgets back itself.
+  }
+
+  /**
+   * Keeps the editor down to the credentials that the chosen authentication 
type actually uses. The
+   * three types have nothing in common, so leaving all of them on screen 
means most of the fields
+   * can only ever stay empty.
+   */
+  private void hideFieldsThatDoNotApply(GuiCompositeWidgets compositeWidgets) {
+    AwsSecretsManagerAuthType authType = readAuthType(compositeWidgets);
+
+    Set<String> hidden = new HashSet<>();
+    if (authType != AwsSecretsManagerAuthType.ACCESS_KEYS) {
+      hidden.add(ID_ACCESS_KEY);
+      hidden.add(ID_SECRET_KEY);
+      hidden.add(ID_SESSION_TOKEN);
+    }
+    if (authType != AwsSecretsManagerAuthType.CREDENTIALS_FILE) {
+      hidden.add(ID_CREDENTIALS_FILE);
+      hidden.add(ID_PROFILE_NAME);
+    }
+    compositeWidgets.setWidgetsHidden(this, hidden);
+  }
+
+  /**
+   * Reads the authentication type from the combo box rather than from this 
object, since the widget
+   * is what the user just changed and the metadata is only written back later.
+   */
+  private AwsSecretsManagerAuthType readAuthType(GuiCompositeWidgets 
compositeWidgets) {
+    Control control = 
compositeWidgets.getWidgetsMap().get(ID_AUTHENTICATION_TYPE);
+    if (control instanceof Combo combo) {
+      try {
+        return AwsSecretsManagerAuthType.valueOf(combo.getText());
+      } catch (IllegalArgumentException e) {
+        // Nothing picked yet, so fall through to what the metadata holds.
+      }
+    }
+    try {
+      return parseAuthType(authenticationType);
+    } catch (HopException e) {
+      return AwsSecretsManagerAuthType.AUTOMATIC;
+    }
+  }
+
+  @Override
+  public String getPluginId() {
+    return "AwsSecretsManager";
+  }
+
+  @Override
+  public String getPluginName() {
+    return "AWS Secrets Manager Variable Resolver";
+  }
+
+  /** A secret value with the point in time after which it should be looked up 
again. */
+  private static class CachedSecret {
+    private final String value;
+    private final long expiryTime;
+
+    private CachedSecret(String value, long expiryTime) {
+      this.value = value;
+      this.expiryTime = expiryTime;
+    }
+
+    private boolean isExpired() {
+      return System.currentTimeMillis() >= expiryTime;
+    }
+  }
+}
diff --git 
a/plugins/tech/aws/src/main/resources/org/apache/hop/core/variables/resolver/aws/messages/messages_de_DE.properties
 
b/plugins/tech/aws/src/main/resources/org/apache/hop/core/variables/resolver/aws/messages/messages_de_DE.properties
new file mode 100644
index 0000000000..6a24e454e0
--- /dev/null
+++ 
b/plugins/tech/aws/src/main/resources/org/apache/hop/core/variables/resolver/aws/messages/messages_de_DE.properties
@@ -0,0 +1,39 @@
+#
+# 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.
+#
+
+AwsSecretsManagerVariableResolver.Region.Label=AWS region
+AwsSecretsManagerVariableResolver.Region.Tooltip=The AWS region holding the 
secrets, for example eu-west-1. Leave empty to use AWS_REGION or the active 
profile.
+AwsSecretsManagerVariableResolver.AuthenticationType.Label=Authentication type
+AwsSecretsManagerVariableResolver.AuthenticationType.Tooltip=AUTOMATIC lets 
the AWS SDK find credentials itself (environment, profile, container or 
instance role). ACCESS_KEYS and CREDENTIALS_FILE use the options below.
+AwsSecretsManagerVariableResolver.AccessKey.Label=Access key
+AwsSecretsManagerVariableResolver.AccessKey.Tooltip=The AWS access key ID. 
Only used with authentication type ACCESS_KEYS.
+AwsSecretsManagerVariableResolver.SecretKey.Label=Secret key
+AwsSecretsManagerVariableResolver.SecretKey.Tooltip=The AWS secret access key. 
Only used with authentication type ACCESS_KEYS.
+AwsSecretsManagerVariableResolver.SessionToken.Label=Session token
+AwsSecretsManagerVariableResolver.SessionToken.Tooltip=The session token for 
temporary credentials. Optional, only used with authentication type ACCESS_KEYS.
+AwsSecretsManagerVariableResolver.CredentialsFile.Label=Credentials file
+AwsSecretsManagerVariableResolver.CredentialsFile.Tooltip=Path to an AWS 
credentials file. Only used with authentication type CREDENTIALS_FILE. Leave 
empty to use the standard location.
+AwsSecretsManagerVariableResolver.ProfileName.Label=Profile name
+AwsSecretsManagerVariableResolver.ProfileName.Tooltip=The profile to read from 
the credentials file. Defaults to "default".
+AwsSecretsManagerVariableResolver.EndpointOverride.Label=Endpoint override
+AwsSecretsManagerVariableResolver.EndpointOverride.Tooltip=An alternative 
Secrets Manager endpoint, for example a VPC endpoint or a local test service. 
Leave empty to use the regional AWS endpoint.
+AwsSecretsManagerVariableResolver.SecretNamePrefix.Label=Secret name prefix
+AwsSecretsManagerVariableResolver.SecretNamePrefix.Tooltip=Prefix placed in 
front of every secret name, for example "production/". Lets you keep 
expressions short.
+AwsSecretsManagerVariableResolver.VersionStage.Label=Version stage
+AwsSecretsManagerVariableResolver.VersionStage.Tooltip=The staging label to 
read, AWSCURRENT by default. Use AWSPREVIOUS to read the version from before 
the last rotation.
+AwsSecretsManagerVariableResolver.CacheTtlSeconds.Label=Cache TTL (seconds)
+AwsSecretsManagerVariableResolver.CacheTtlSeconds.Tooltip=How long a retrieved 
secret is reused before it is looked up again. 0 disables caching. Secrets 
Manager bills per API call, so a small value reduces cost, but rotated secrets 
take up to this long to be picked up.
diff --git 
a/plugins/tech/aws/src/main/resources/org/apache/hop/core/variables/resolver/aws/messages/messages_en_US.properties
 
b/plugins/tech/aws/src/main/resources/org/apache/hop/core/variables/resolver/aws/messages/messages_en_US.properties
new file mode 100644
index 0000000000..6a24e454e0
--- /dev/null
+++ 
b/plugins/tech/aws/src/main/resources/org/apache/hop/core/variables/resolver/aws/messages/messages_en_US.properties
@@ -0,0 +1,39 @@
+#
+# 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.
+#
+
+AwsSecretsManagerVariableResolver.Region.Label=AWS region
+AwsSecretsManagerVariableResolver.Region.Tooltip=The AWS region holding the 
secrets, for example eu-west-1. Leave empty to use AWS_REGION or the active 
profile.
+AwsSecretsManagerVariableResolver.AuthenticationType.Label=Authentication type
+AwsSecretsManagerVariableResolver.AuthenticationType.Tooltip=AUTOMATIC lets 
the AWS SDK find credentials itself (environment, profile, container or 
instance role). ACCESS_KEYS and CREDENTIALS_FILE use the options below.
+AwsSecretsManagerVariableResolver.AccessKey.Label=Access key
+AwsSecretsManagerVariableResolver.AccessKey.Tooltip=The AWS access key ID. 
Only used with authentication type ACCESS_KEYS.
+AwsSecretsManagerVariableResolver.SecretKey.Label=Secret key
+AwsSecretsManagerVariableResolver.SecretKey.Tooltip=The AWS secret access key. 
Only used with authentication type ACCESS_KEYS.
+AwsSecretsManagerVariableResolver.SessionToken.Label=Session token
+AwsSecretsManagerVariableResolver.SessionToken.Tooltip=The session token for 
temporary credentials. Optional, only used with authentication type ACCESS_KEYS.
+AwsSecretsManagerVariableResolver.CredentialsFile.Label=Credentials file
+AwsSecretsManagerVariableResolver.CredentialsFile.Tooltip=Path to an AWS 
credentials file. Only used with authentication type CREDENTIALS_FILE. Leave 
empty to use the standard location.
+AwsSecretsManagerVariableResolver.ProfileName.Label=Profile name
+AwsSecretsManagerVariableResolver.ProfileName.Tooltip=The profile to read from 
the credentials file. Defaults to "default".
+AwsSecretsManagerVariableResolver.EndpointOverride.Label=Endpoint override
+AwsSecretsManagerVariableResolver.EndpointOverride.Tooltip=An alternative 
Secrets Manager endpoint, for example a VPC endpoint or a local test service. 
Leave empty to use the regional AWS endpoint.
+AwsSecretsManagerVariableResolver.SecretNamePrefix.Label=Secret name prefix
+AwsSecretsManagerVariableResolver.SecretNamePrefix.Tooltip=Prefix placed in 
front of every secret name, for example "production/". Lets you keep 
expressions short.
+AwsSecretsManagerVariableResolver.VersionStage.Label=Version stage
+AwsSecretsManagerVariableResolver.VersionStage.Tooltip=The staging label to 
read, AWSCURRENT by default. Use AWSPREVIOUS to read the version from before 
the last rotation.
+AwsSecretsManagerVariableResolver.CacheTtlSeconds.Label=Cache TTL (seconds)
+AwsSecretsManagerVariableResolver.CacheTtlSeconds.Tooltip=How long a retrieved 
secret is reused before it is looked up again. 0 disables caching. Secrets 
Manager bills per API call, so a small value reduces cost, but rotated secrets 
take up to this long to be picked up.
diff --git 
a/plugins/tech/aws/src/main/resources/org/apache/hop/core/variables/resolver/aws/messages/messages_es_AR.properties
 
b/plugins/tech/aws/src/main/resources/org/apache/hop/core/variables/resolver/aws/messages/messages_es_AR.properties
new file mode 100644
index 0000000000..6a24e454e0
--- /dev/null
+++ 
b/plugins/tech/aws/src/main/resources/org/apache/hop/core/variables/resolver/aws/messages/messages_es_AR.properties
@@ -0,0 +1,39 @@
+#
+# 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.
+#
+
+AwsSecretsManagerVariableResolver.Region.Label=AWS region
+AwsSecretsManagerVariableResolver.Region.Tooltip=The AWS region holding the 
secrets, for example eu-west-1. Leave empty to use AWS_REGION or the active 
profile.
+AwsSecretsManagerVariableResolver.AuthenticationType.Label=Authentication type
+AwsSecretsManagerVariableResolver.AuthenticationType.Tooltip=AUTOMATIC lets 
the AWS SDK find credentials itself (environment, profile, container or 
instance role). ACCESS_KEYS and CREDENTIALS_FILE use the options below.
+AwsSecretsManagerVariableResolver.AccessKey.Label=Access key
+AwsSecretsManagerVariableResolver.AccessKey.Tooltip=The AWS access key ID. 
Only used with authentication type ACCESS_KEYS.
+AwsSecretsManagerVariableResolver.SecretKey.Label=Secret key
+AwsSecretsManagerVariableResolver.SecretKey.Tooltip=The AWS secret access key. 
Only used with authentication type ACCESS_KEYS.
+AwsSecretsManagerVariableResolver.SessionToken.Label=Session token
+AwsSecretsManagerVariableResolver.SessionToken.Tooltip=The session token for 
temporary credentials. Optional, only used with authentication type ACCESS_KEYS.
+AwsSecretsManagerVariableResolver.CredentialsFile.Label=Credentials file
+AwsSecretsManagerVariableResolver.CredentialsFile.Tooltip=Path to an AWS 
credentials file. Only used with authentication type CREDENTIALS_FILE. Leave 
empty to use the standard location.
+AwsSecretsManagerVariableResolver.ProfileName.Label=Profile name
+AwsSecretsManagerVariableResolver.ProfileName.Tooltip=The profile to read from 
the credentials file. Defaults to "default".
+AwsSecretsManagerVariableResolver.EndpointOverride.Label=Endpoint override
+AwsSecretsManagerVariableResolver.EndpointOverride.Tooltip=An alternative 
Secrets Manager endpoint, for example a VPC endpoint or a local test service. 
Leave empty to use the regional AWS endpoint.
+AwsSecretsManagerVariableResolver.SecretNamePrefix.Label=Secret name prefix
+AwsSecretsManagerVariableResolver.SecretNamePrefix.Tooltip=Prefix placed in 
front of every secret name, for example "production/". Lets you keep 
expressions short.
+AwsSecretsManagerVariableResolver.VersionStage.Label=Version stage
+AwsSecretsManagerVariableResolver.VersionStage.Tooltip=The staging label to 
read, AWSCURRENT by default. Use AWSPREVIOUS to read the version from before 
the last rotation.
+AwsSecretsManagerVariableResolver.CacheTtlSeconds.Label=Cache TTL (seconds)
+AwsSecretsManagerVariableResolver.CacheTtlSeconds.Tooltip=How long a retrieved 
secret is reused before it is looked up again. 0 disables caching. Secrets 
Manager bills per API call, so a small value reduces cost, but rotated secrets 
take up to this long to be picked up.
diff --git 
a/plugins/tech/aws/src/main/resources/org/apache/hop/core/variables/resolver/aws/messages/messages_es_ES.properties
 
b/plugins/tech/aws/src/main/resources/org/apache/hop/core/variables/resolver/aws/messages/messages_es_ES.properties
new file mode 100644
index 0000000000..6a24e454e0
--- /dev/null
+++ 
b/plugins/tech/aws/src/main/resources/org/apache/hop/core/variables/resolver/aws/messages/messages_es_ES.properties
@@ -0,0 +1,39 @@
+#
+# 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.
+#
+
+AwsSecretsManagerVariableResolver.Region.Label=AWS region
+AwsSecretsManagerVariableResolver.Region.Tooltip=The AWS region holding the 
secrets, for example eu-west-1. Leave empty to use AWS_REGION or the active 
profile.
+AwsSecretsManagerVariableResolver.AuthenticationType.Label=Authentication type
+AwsSecretsManagerVariableResolver.AuthenticationType.Tooltip=AUTOMATIC lets 
the AWS SDK find credentials itself (environment, profile, container or 
instance role). ACCESS_KEYS and CREDENTIALS_FILE use the options below.
+AwsSecretsManagerVariableResolver.AccessKey.Label=Access key
+AwsSecretsManagerVariableResolver.AccessKey.Tooltip=The AWS access key ID. 
Only used with authentication type ACCESS_KEYS.
+AwsSecretsManagerVariableResolver.SecretKey.Label=Secret key
+AwsSecretsManagerVariableResolver.SecretKey.Tooltip=The AWS secret access key. 
Only used with authentication type ACCESS_KEYS.
+AwsSecretsManagerVariableResolver.SessionToken.Label=Session token
+AwsSecretsManagerVariableResolver.SessionToken.Tooltip=The session token for 
temporary credentials. Optional, only used with authentication type ACCESS_KEYS.
+AwsSecretsManagerVariableResolver.CredentialsFile.Label=Credentials file
+AwsSecretsManagerVariableResolver.CredentialsFile.Tooltip=Path to an AWS 
credentials file. Only used with authentication type CREDENTIALS_FILE. Leave 
empty to use the standard location.
+AwsSecretsManagerVariableResolver.ProfileName.Label=Profile name
+AwsSecretsManagerVariableResolver.ProfileName.Tooltip=The profile to read from 
the credentials file. Defaults to "default".
+AwsSecretsManagerVariableResolver.EndpointOverride.Label=Endpoint override
+AwsSecretsManagerVariableResolver.EndpointOverride.Tooltip=An alternative 
Secrets Manager endpoint, for example a VPC endpoint or a local test service. 
Leave empty to use the regional AWS endpoint.
+AwsSecretsManagerVariableResolver.SecretNamePrefix.Label=Secret name prefix
+AwsSecretsManagerVariableResolver.SecretNamePrefix.Tooltip=Prefix placed in 
front of every secret name, for example "production/". Lets you keep 
expressions short.
+AwsSecretsManagerVariableResolver.VersionStage.Label=Version stage
+AwsSecretsManagerVariableResolver.VersionStage.Tooltip=The staging label to 
read, AWSCURRENT by default. Use AWSPREVIOUS to read the version from before 
the last rotation.
+AwsSecretsManagerVariableResolver.CacheTtlSeconds.Label=Cache TTL (seconds)
+AwsSecretsManagerVariableResolver.CacheTtlSeconds.Tooltip=How long a retrieved 
secret is reused before it is looked up again. 0 disables caching. Secrets 
Manager bills per API call, so a small value reduces cost, but rotated secrets 
take up to this long to be picked up.
diff --git 
a/plugins/tech/aws/src/main/resources/org/apache/hop/core/variables/resolver/aws/messages/messages_fr_FR.properties
 
b/plugins/tech/aws/src/main/resources/org/apache/hop/core/variables/resolver/aws/messages/messages_fr_FR.properties
new file mode 100644
index 0000000000..6a24e454e0
--- /dev/null
+++ 
b/plugins/tech/aws/src/main/resources/org/apache/hop/core/variables/resolver/aws/messages/messages_fr_FR.properties
@@ -0,0 +1,39 @@
+#
+# 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.
+#
+
+AwsSecretsManagerVariableResolver.Region.Label=AWS region
+AwsSecretsManagerVariableResolver.Region.Tooltip=The AWS region holding the 
secrets, for example eu-west-1. Leave empty to use AWS_REGION or the active 
profile.
+AwsSecretsManagerVariableResolver.AuthenticationType.Label=Authentication type
+AwsSecretsManagerVariableResolver.AuthenticationType.Tooltip=AUTOMATIC lets 
the AWS SDK find credentials itself (environment, profile, container or 
instance role). ACCESS_KEYS and CREDENTIALS_FILE use the options below.
+AwsSecretsManagerVariableResolver.AccessKey.Label=Access key
+AwsSecretsManagerVariableResolver.AccessKey.Tooltip=The AWS access key ID. 
Only used with authentication type ACCESS_KEYS.
+AwsSecretsManagerVariableResolver.SecretKey.Label=Secret key
+AwsSecretsManagerVariableResolver.SecretKey.Tooltip=The AWS secret access key. 
Only used with authentication type ACCESS_KEYS.
+AwsSecretsManagerVariableResolver.SessionToken.Label=Session token
+AwsSecretsManagerVariableResolver.SessionToken.Tooltip=The session token for 
temporary credentials. Optional, only used with authentication type ACCESS_KEYS.
+AwsSecretsManagerVariableResolver.CredentialsFile.Label=Credentials file
+AwsSecretsManagerVariableResolver.CredentialsFile.Tooltip=Path to an AWS 
credentials file. Only used with authentication type CREDENTIALS_FILE. Leave 
empty to use the standard location.
+AwsSecretsManagerVariableResolver.ProfileName.Label=Profile name
+AwsSecretsManagerVariableResolver.ProfileName.Tooltip=The profile to read from 
the credentials file. Defaults to "default".
+AwsSecretsManagerVariableResolver.EndpointOverride.Label=Endpoint override
+AwsSecretsManagerVariableResolver.EndpointOverride.Tooltip=An alternative 
Secrets Manager endpoint, for example a VPC endpoint or a local test service. 
Leave empty to use the regional AWS endpoint.
+AwsSecretsManagerVariableResolver.SecretNamePrefix.Label=Secret name prefix
+AwsSecretsManagerVariableResolver.SecretNamePrefix.Tooltip=Prefix placed in 
front of every secret name, for example "production/". Lets you keep 
expressions short.
+AwsSecretsManagerVariableResolver.VersionStage.Label=Version stage
+AwsSecretsManagerVariableResolver.VersionStage.Tooltip=The staging label to 
read, AWSCURRENT by default. Use AWSPREVIOUS to read the version from before 
the last rotation.
+AwsSecretsManagerVariableResolver.CacheTtlSeconds.Label=Cache TTL (seconds)
+AwsSecretsManagerVariableResolver.CacheTtlSeconds.Tooltip=How long a retrieved 
secret is reused before it is looked up again. 0 disables caching. Secrets 
Manager bills per API call, so a small value reduces cost, but rotated secrets 
take up to this long to be picked up.
diff --git 
a/plugins/tech/aws/src/main/resources/org/apache/hop/core/variables/resolver/aws/messages/messages_it_IT.properties
 
b/plugins/tech/aws/src/main/resources/org/apache/hop/core/variables/resolver/aws/messages/messages_it_IT.properties
new file mode 100644
index 0000000000..6a24e454e0
--- /dev/null
+++ 
b/plugins/tech/aws/src/main/resources/org/apache/hop/core/variables/resolver/aws/messages/messages_it_IT.properties
@@ -0,0 +1,39 @@
+#
+# 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.
+#
+
+AwsSecretsManagerVariableResolver.Region.Label=AWS region
+AwsSecretsManagerVariableResolver.Region.Tooltip=The AWS region holding the 
secrets, for example eu-west-1. Leave empty to use AWS_REGION or the active 
profile.
+AwsSecretsManagerVariableResolver.AuthenticationType.Label=Authentication type
+AwsSecretsManagerVariableResolver.AuthenticationType.Tooltip=AUTOMATIC lets 
the AWS SDK find credentials itself (environment, profile, container or 
instance role). ACCESS_KEYS and CREDENTIALS_FILE use the options below.
+AwsSecretsManagerVariableResolver.AccessKey.Label=Access key
+AwsSecretsManagerVariableResolver.AccessKey.Tooltip=The AWS access key ID. 
Only used with authentication type ACCESS_KEYS.
+AwsSecretsManagerVariableResolver.SecretKey.Label=Secret key
+AwsSecretsManagerVariableResolver.SecretKey.Tooltip=The AWS secret access key. 
Only used with authentication type ACCESS_KEYS.
+AwsSecretsManagerVariableResolver.SessionToken.Label=Session token
+AwsSecretsManagerVariableResolver.SessionToken.Tooltip=The session token for 
temporary credentials. Optional, only used with authentication type ACCESS_KEYS.
+AwsSecretsManagerVariableResolver.CredentialsFile.Label=Credentials file
+AwsSecretsManagerVariableResolver.CredentialsFile.Tooltip=Path to an AWS 
credentials file. Only used with authentication type CREDENTIALS_FILE. Leave 
empty to use the standard location.
+AwsSecretsManagerVariableResolver.ProfileName.Label=Profile name
+AwsSecretsManagerVariableResolver.ProfileName.Tooltip=The profile to read from 
the credentials file. Defaults to "default".
+AwsSecretsManagerVariableResolver.EndpointOverride.Label=Endpoint override
+AwsSecretsManagerVariableResolver.EndpointOverride.Tooltip=An alternative 
Secrets Manager endpoint, for example a VPC endpoint or a local test service. 
Leave empty to use the regional AWS endpoint.
+AwsSecretsManagerVariableResolver.SecretNamePrefix.Label=Secret name prefix
+AwsSecretsManagerVariableResolver.SecretNamePrefix.Tooltip=Prefix placed in 
front of every secret name, for example "production/". Lets you keep 
expressions short.
+AwsSecretsManagerVariableResolver.VersionStage.Label=Version stage
+AwsSecretsManagerVariableResolver.VersionStage.Tooltip=The staging label to 
read, AWSCURRENT by default. Use AWSPREVIOUS to read the version from before 
the last rotation.
+AwsSecretsManagerVariableResolver.CacheTtlSeconds.Label=Cache TTL (seconds)
+AwsSecretsManagerVariableResolver.CacheTtlSeconds.Tooltip=How long a retrieved 
secret is reused before it is looked up again. 0 disables caching. Secrets 
Manager bills per API call, so a small value reduces cost, but rotated secrets 
take up to this long to be picked up.
diff --git 
a/plugins/tech/aws/src/main/resources/org/apache/hop/core/variables/resolver/aws/messages/messages_ja_JP.properties
 
b/plugins/tech/aws/src/main/resources/org/apache/hop/core/variables/resolver/aws/messages/messages_ja_JP.properties
new file mode 100644
index 0000000000..6a24e454e0
--- /dev/null
+++ 
b/plugins/tech/aws/src/main/resources/org/apache/hop/core/variables/resolver/aws/messages/messages_ja_JP.properties
@@ -0,0 +1,39 @@
+#
+# 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.
+#
+
+AwsSecretsManagerVariableResolver.Region.Label=AWS region
+AwsSecretsManagerVariableResolver.Region.Tooltip=The AWS region holding the 
secrets, for example eu-west-1. Leave empty to use AWS_REGION or the active 
profile.
+AwsSecretsManagerVariableResolver.AuthenticationType.Label=Authentication type
+AwsSecretsManagerVariableResolver.AuthenticationType.Tooltip=AUTOMATIC lets 
the AWS SDK find credentials itself (environment, profile, container or 
instance role). ACCESS_KEYS and CREDENTIALS_FILE use the options below.
+AwsSecretsManagerVariableResolver.AccessKey.Label=Access key
+AwsSecretsManagerVariableResolver.AccessKey.Tooltip=The AWS access key ID. 
Only used with authentication type ACCESS_KEYS.
+AwsSecretsManagerVariableResolver.SecretKey.Label=Secret key
+AwsSecretsManagerVariableResolver.SecretKey.Tooltip=The AWS secret access key. 
Only used with authentication type ACCESS_KEYS.
+AwsSecretsManagerVariableResolver.SessionToken.Label=Session token
+AwsSecretsManagerVariableResolver.SessionToken.Tooltip=The session token for 
temporary credentials. Optional, only used with authentication type ACCESS_KEYS.
+AwsSecretsManagerVariableResolver.CredentialsFile.Label=Credentials file
+AwsSecretsManagerVariableResolver.CredentialsFile.Tooltip=Path to an AWS 
credentials file. Only used with authentication type CREDENTIALS_FILE. Leave 
empty to use the standard location.
+AwsSecretsManagerVariableResolver.ProfileName.Label=Profile name
+AwsSecretsManagerVariableResolver.ProfileName.Tooltip=The profile to read from 
the credentials file. Defaults to "default".
+AwsSecretsManagerVariableResolver.EndpointOverride.Label=Endpoint override
+AwsSecretsManagerVariableResolver.EndpointOverride.Tooltip=An alternative 
Secrets Manager endpoint, for example a VPC endpoint or a local test service. 
Leave empty to use the regional AWS endpoint.
+AwsSecretsManagerVariableResolver.SecretNamePrefix.Label=Secret name prefix
+AwsSecretsManagerVariableResolver.SecretNamePrefix.Tooltip=Prefix placed in 
front of every secret name, for example "production/". Lets you keep 
expressions short.
+AwsSecretsManagerVariableResolver.VersionStage.Label=Version stage
+AwsSecretsManagerVariableResolver.VersionStage.Tooltip=The staging label to 
read, AWSCURRENT by default. Use AWSPREVIOUS to read the version from before 
the last rotation.
+AwsSecretsManagerVariableResolver.CacheTtlSeconds.Label=Cache TTL (seconds)
+AwsSecretsManagerVariableResolver.CacheTtlSeconds.Tooltip=How long a retrieved 
secret is reused before it is looked up again. 0 disables caching. Secrets 
Manager bills per API call, so a small value reduces cost, but rotated secrets 
take up to this long to be picked up.
diff --git 
a/plugins/tech/aws/src/main/resources/org/apache/hop/core/variables/resolver/aws/messages/messages_ko_KR.properties
 
b/plugins/tech/aws/src/main/resources/org/apache/hop/core/variables/resolver/aws/messages/messages_ko_KR.properties
new file mode 100644
index 0000000000..6a24e454e0
--- /dev/null
+++ 
b/plugins/tech/aws/src/main/resources/org/apache/hop/core/variables/resolver/aws/messages/messages_ko_KR.properties
@@ -0,0 +1,39 @@
+#
+# 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.
+#
+
+AwsSecretsManagerVariableResolver.Region.Label=AWS region
+AwsSecretsManagerVariableResolver.Region.Tooltip=The AWS region holding the 
secrets, for example eu-west-1. Leave empty to use AWS_REGION or the active 
profile.
+AwsSecretsManagerVariableResolver.AuthenticationType.Label=Authentication type
+AwsSecretsManagerVariableResolver.AuthenticationType.Tooltip=AUTOMATIC lets 
the AWS SDK find credentials itself (environment, profile, container or 
instance role). ACCESS_KEYS and CREDENTIALS_FILE use the options below.
+AwsSecretsManagerVariableResolver.AccessKey.Label=Access key
+AwsSecretsManagerVariableResolver.AccessKey.Tooltip=The AWS access key ID. 
Only used with authentication type ACCESS_KEYS.
+AwsSecretsManagerVariableResolver.SecretKey.Label=Secret key
+AwsSecretsManagerVariableResolver.SecretKey.Tooltip=The AWS secret access key. 
Only used with authentication type ACCESS_KEYS.
+AwsSecretsManagerVariableResolver.SessionToken.Label=Session token
+AwsSecretsManagerVariableResolver.SessionToken.Tooltip=The session token for 
temporary credentials. Optional, only used with authentication type ACCESS_KEYS.
+AwsSecretsManagerVariableResolver.CredentialsFile.Label=Credentials file
+AwsSecretsManagerVariableResolver.CredentialsFile.Tooltip=Path to an AWS 
credentials file. Only used with authentication type CREDENTIALS_FILE. Leave 
empty to use the standard location.
+AwsSecretsManagerVariableResolver.ProfileName.Label=Profile name
+AwsSecretsManagerVariableResolver.ProfileName.Tooltip=The profile to read from 
the credentials file. Defaults to "default".
+AwsSecretsManagerVariableResolver.EndpointOverride.Label=Endpoint override
+AwsSecretsManagerVariableResolver.EndpointOverride.Tooltip=An alternative 
Secrets Manager endpoint, for example a VPC endpoint or a local test service. 
Leave empty to use the regional AWS endpoint.
+AwsSecretsManagerVariableResolver.SecretNamePrefix.Label=Secret name prefix
+AwsSecretsManagerVariableResolver.SecretNamePrefix.Tooltip=Prefix placed in 
front of every secret name, for example "production/". Lets you keep 
expressions short.
+AwsSecretsManagerVariableResolver.VersionStage.Label=Version stage
+AwsSecretsManagerVariableResolver.VersionStage.Tooltip=The staging label to 
read, AWSCURRENT by default. Use AWSPREVIOUS to read the version from before 
the last rotation.
+AwsSecretsManagerVariableResolver.CacheTtlSeconds.Label=Cache TTL (seconds)
+AwsSecretsManagerVariableResolver.CacheTtlSeconds.Tooltip=How long a retrieved 
secret is reused before it is looked up again. 0 disables caching. Secrets 
Manager bills per API call, so a small value reduces cost, but rotated secrets 
take up to this long to be picked up.
diff --git 
a/plugins/tech/aws/src/main/resources/org/apache/hop/core/variables/resolver/aws/messages/messages_nl_NL.properties
 
b/plugins/tech/aws/src/main/resources/org/apache/hop/core/variables/resolver/aws/messages/messages_nl_NL.properties
new file mode 100644
index 0000000000..6a24e454e0
--- /dev/null
+++ 
b/plugins/tech/aws/src/main/resources/org/apache/hop/core/variables/resolver/aws/messages/messages_nl_NL.properties
@@ -0,0 +1,39 @@
+#
+# 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.
+#
+
+AwsSecretsManagerVariableResolver.Region.Label=AWS region
+AwsSecretsManagerVariableResolver.Region.Tooltip=The AWS region holding the 
secrets, for example eu-west-1. Leave empty to use AWS_REGION or the active 
profile.
+AwsSecretsManagerVariableResolver.AuthenticationType.Label=Authentication type
+AwsSecretsManagerVariableResolver.AuthenticationType.Tooltip=AUTOMATIC lets 
the AWS SDK find credentials itself (environment, profile, container or 
instance role). ACCESS_KEYS and CREDENTIALS_FILE use the options below.
+AwsSecretsManagerVariableResolver.AccessKey.Label=Access key
+AwsSecretsManagerVariableResolver.AccessKey.Tooltip=The AWS access key ID. 
Only used with authentication type ACCESS_KEYS.
+AwsSecretsManagerVariableResolver.SecretKey.Label=Secret key
+AwsSecretsManagerVariableResolver.SecretKey.Tooltip=The AWS secret access key. 
Only used with authentication type ACCESS_KEYS.
+AwsSecretsManagerVariableResolver.SessionToken.Label=Session token
+AwsSecretsManagerVariableResolver.SessionToken.Tooltip=The session token for 
temporary credentials. Optional, only used with authentication type ACCESS_KEYS.
+AwsSecretsManagerVariableResolver.CredentialsFile.Label=Credentials file
+AwsSecretsManagerVariableResolver.CredentialsFile.Tooltip=Path to an AWS 
credentials file. Only used with authentication type CREDENTIALS_FILE. Leave 
empty to use the standard location.
+AwsSecretsManagerVariableResolver.ProfileName.Label=Profile name
+AwsSecretsManagerVariableResolver.ProfileName.Tooltip=The profile to read from 
the credentials file. Defaults to "default".
+AwsSecretsManagerVariableResolver.EndpointOverride.Label=Endpoint override
+AwsSecretsManagerVariableResolver.EndpointOverride.Tooltip=An alternative 
Secrets Manager endpoint, for example a VPC endpoint or a local test service. 
Leave empty to use the regional AWS endpoint.
+AwsSecretsManagerVariableResolver.SecretNamePrefix.Label=Secret name prefix
+AwsSecretsManagerVariableResolver.SecretNamePrefix.Tooltip=Prefix placed in 
front of every secret name, for example "production/". Lets you keep 
expressions short.
+AwsSecretsManagerVariableResolver.VersionStage.Label=Version stage
+AwsSecretsManagerVariableResolver.VersionStage.Tooltip=The staging label to 
read, AWSCURRENT by default. Use AWSPREVIOUS to read the version from before 
the last rotation.
+AwsSecretsManagerVariableResolver.CacheTtlSeconds.Label=Cache TTL (seconds)
+AwsSecretsManagerVariableResolver.CacheTtlSeconds.Tooltip=How long a retrieved 
secret is reused before it is looked up again. 0 disables caching. Secrets 
Manager bills per API call, so a small value reduces cost, but rotated secrets 
take up to this long to be picked up.
diff --git 
a/plugins/tech/aws/src/main/resources/org/apache/hop/core/variables/resolver/aws/messages/messages_no_NO.properties
 
b/plugins/tech/aws/src/main/resources/org/apache/hop/core/variables/resolver/aws/messages/messages_no_NO.properties
new file mode 100644
index 0000000000..6a24e454e0
--- /dev/null
+++ 
b/plugins/tech/aws/src/main/resources/org/apache/hop/core/variables/resolver/aws/messages/messages_no_NO.properties
@@ -0,0 +1,39 @@
+#
+# 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.
+#
+
+AwsSecretsManagerVariableResolver.Region.Label=AWS region
+AwsSecretsManagerVariableResolver.Region.Tooltip=The AWS region holding the 
secrets, for example eu-west-1. Leave empty to use AWS_REGION or the active 
profile.
+AwsSecretsManagerVariableResolver.AuthenticationType.Label=Authentication type
+AwsSecretsManagerVariableResolver.AuthenticationType.Tooltip=AUTOMATIC lets 
the AWS SDK find credentials itself (environment, profile, container or 
instance role). ACCESS_KEYS and CREDENTIALS_FILE use the options below.
+AwsSecretsManagerVariableResolver.AccessKey.Label=Access key
+AwsSecretsManagerVariableResolver.AccessKey.Tooltip=The AWS access key ID. 
Only used with authentication type ACCESS_KEYS.
+AwsSecretsManagerVariableResolver.SecretKey.Label=Secret key
+AwsSecretsManagerVariableResolver.SecretKey.Tooltip=The AWS secret access key. 
Only used with authentication type ACCESS_KEYS.
+AwsSecretsManagerVariableResolver.SessionToken.Label=Session token
+AwsSecretsManagerVariableResolver.SessionToken.Tooltip=The session token for 
temporary credentials. Optional, only used with authentication type ACCESS_KEYS.
+AwsSecretsManagerVariableResolver.CredentialsFile.Label=Credentials file
+AwsSecretsManagerVariableResolver.CredentialsFile.Tooltip=Path to an AWS 
credentials file. Only used with authentication type CREDENTIALS_FILE. Leave 
empty to use the standard location.
+AwsSecretsManagerVariableResolver.ProfileName.Label=Profile name
+AwsSecretsManagerVariableResolver.ProfileName.Tooltip=The profile to read from 
the credentials file. Defaults to "default".
+AwsSecretsManagerVariableResolver.EndpointOverride.Label=Endpoint override
+AwsSecretsManagerVariableResolver.EndpointOverride.Tooltip=An alternative 
Secrets Manager endpoint, for example a VPC endpoint or a local test service. 
Leave empty to use the regional AWS endpoint.
+AwsSecretsManagerVariableResolver.SecretNamePrefix.Label=Secret name prefix
+AwsSecretsManagerVariableResolver.SecretNamePrefix.Tooltip=Prefix placed in 
front of every secret name, for example "production/". Lets you keep 
expressions short.
+AwsSecretsManagerVariableResolver.VersionStage.Label=Version stage
+AwsSecretsManagerVariableResolver.VersionStage.Tooltip=The staging label to 
read, AWSCURRENT by default. Use AWSPREVIOUS to read the version from before 
the last rotation.
+AwsSecretsManagerVariableResolver.CacheTtlSeconds.Label=Cache TTL (seconds)
+AwsSecretsManagerVariableResolver.CacheTtlSeconds.Tooltip=How long a retrieved 
secret is reused before it is looked up again. 0 disables caching. Secrets 
Manager bills per API call, so a small value reduces cost, but rotated secrets 
take up to this long to be picked up.
diff --git 
a/plugins/tech/aws/src/main/resources/org/apache/hop/core/variables/resolver/aws/messages/messages_pl_PL.properties
 
b/plugins/tech/aws/src/main/resources/org/apache/hop/core/variables/resolver/aws/messages/messages_pl_PL.properties
new file mode 100644
index 0000000000..6a24e454e0
--- /dev/null
+++ 
b/plugins/tech/aws/src/main/resources/org/apache/hop/core/variables/resolver/aws/messages/messages_pl_PL.properties
@@ -0,0 +1,39 @@
+#
+# 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.
+#
+
+AwsSecretsManagerVariableResolver.Region.Label=AWS region
+AwsSecretsManagerVariableResolver.Region.Tooltip=The AWS region holding the 
secrets, for example eu-west-1. Leave empty to use AWS_REGION or the active 
profile.
+AwsSecretsManagerVariableResolver.AuthenticationType.Label=Authentication type
+AwsSecretsManagerVariableResolver.AuthenticationType.Tooltip=AUTOMATIC lets 
the AWS SDK find credentials itself (environment, profile, container or 
instance role). ACCESS_KEYS and CREDENTIALS_FILE use the options below.
+AwsSecretsManagerVariableResolver.AccessKey.Label=Access key
+AwsSecretsManagerVariableResolver.AccessKey.Tooltip=The AWS access key ID. 
Only used with authentication type ACCESS_KEYS.
+AwsSecretsManagerVariableResolver.SecretKey.Label=Secret key
+AwsSecretsManagerVariableResolver.SecretKey.Tooltip=The AWS secret access key. 
Only used with authentication type ACCESS_KEYS.
+AwsSecretsManagerVariableResolver.SessionToken.Label=Session token
+AwsSecretsManagerVariableResolver.SessionToken.Tooltip=The session token for 
temporary credentials. Optional, only used with authentication type ACCESS_KEYS.
+AwsSecretsManagerVariableResolver.CredentialsFile.Label=Credentials file
+AwsSecretsManagerVariableResolver.CredentialsFile.Tooltip=Path to an AWS 
credentials file. Only used with authentication type CREDENTIALS_FILE. Leave 
empty to use the standard location.
+AwsSecretsManagerVariableResolver.ProfileName.Label=Profile name
+AwsSecretsManagerVariableResolver.ProfileName.Tooltip=The profile to read from 
the credentials file. Defaults to "default".
+AwsSecretsManagerVariableResolver.EndpointOverride.Label=Endpoint override
+AwsSecretsManagerVariableResolver.EndpointOverride.Tooltip=An alternative 
Secrets Manager endpoint, for example a VPC endpoint or a local test service. 
Leave empty to use the regional AWS endpoint.
+AwsSecretsManagerVariableResolver.SecretNamePrefix.Label=Secret name prefix
+AwsSecretsManagerVariableResolver.SecretNamePrefix.Tooltip=Prefix placed in 
front of every secret name, for example "production/". Lets you keep 
expressions short.
+AwsSecretsManagerVariableResolver.VersionStage.Label=Version stage
+AwsSecretsManagerVariableResolver.VersionStage.Tooltip=The staging label to 
read, AWSCURRENT by default. Use AWSPREVIOUS to read the version from before 
the last rotation.
+AwsSecretsManagerVariableResolver.CacheTtlSeconds.Label=Cache TTL (seconds)
+AwsSecretsManagerVariableResolver.CacheTtlSeconds.Tooltip=How long a retrieved 
secret is reused before it is looked up again. 0 disables caching. Secrets 
Manager bills per API call, so a small value reduces cost, but rotated secrets 
take up to this long to be picked up.
diff --git 
a/plugins/tech/aws/src/main/resources/org/apache/hop/core/variables/resolver/aws/messages/messages_pt_BR.properties
 
b/plugins/tech/aws/src/main/resources/org/apache/hop/core/variables/resolver/aws/messages/messages_pt_BR.properties
new file mode 100644
index 0000000000..6a24e454e0
--- /dev/null
+++ 
b/plugins/tech/aws/src/main/resources/org/apache/hop/core/variables/resolver/aws/messages/messages_pt_BR.properties
@@ -0,0 +1,39 @@
+#
+# 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.
+#
+
+AwsSecretsManagerVariableResolver.Region.Label=AWS region
+AwsSecretsManagerVariableResolver.Region.Tooltip=The AWS region holding the 
secrets, for example eu-west-1. Leave empty to use AWS_REGION or the active 
profile.
+AwsSecretsManagerVariableResolver.AuthenticationType.Label=Authentication type
+AwsSecretsManagerVariableResolver.AuthenticationType.Tooltip=AUTOMATIC lets 
the AWS SDK find credentials itself (environment, profile, container or 
instance role). ACCESS_KEYS and CREDENTIALS_FILE use the options below.
+AwsSecretsManagerVariableResolver.AccessKey.Label=Access key
+AwsSecretsManagerVariableResolver.AccessKey.Tooltip=The AWS access key ID. 
Only used with authentication type ACCESS_KEYS.
+AwsSecretsManagerVariableResolver.SecretKey.Label=Secret key
+AwsSecretsManagerVariableResolver.SecretKey.Tooltip=The AWS secret access key. 
Only used with authentication type ACCESS_KEYS.
+AwsSecretsManagerVariableResolver.SessionToken.Label=Session token
+AwsSecretsManagerVariableResolver.SessionToken.Tooltip=The session token for 
temporary credentials. Optional, only used with authentication type ACCESS_KEYS.
+AwsSecretsManagerVariableResolver.CredentialsFile.Label=Credentials file
+AwsSecretsManagerVariableResolver.CredentialsFile.Tooltip=Path to an AWS 
credentials file. Only used with authentication type CREDENTIALS_FILE. Leave 
empty to use the standard location.
+AwsSecretsManagerVariableResolver.ProfileName.Label=Profile name
+AwsSecretsManagerVariableResolver.ProfileName.Tooltip=The profile to read from 
the credentials file. Defaults to "default".
+AwsSecretsManagerVariableResolver.EndpointOverride.Label=Endpoint override
+AwsSecretsManagerVariableResolver.EndpointOverride.Tooltip=An alternative 
Secrets Manager endpoint, for example a VPC endpoint or a local test service. 
Leave empty to use the regional AWS endpoint.
+AwsSecretsManagerVariableResolver.SecretNamePrefix.Label=Secret name prefix
+AwsSecretsManagerVariableResolver.SecretNamePrefix.Tooltip=Prefix placed in 
front of every secret name, for example "production/". Lets you keep 
expressions short.
+AwsSecretsManagerVariableResolver.VersionStage.Label=Version stage
+AwsSecretsManagerVariableResolver.VersionStage.Tooltip=The staging label to 
read, AWSCURRENT by default. Use AWSPREVIOUS to read the version from before 
the last rotation.
+AwsSecretsManagerVariableResolver.CacheTtlSeconds.Label=Cache TTL (seconds)
+AwsSecretsManagerVariableResolver.CacheTtlSeconds.Tooltip=How long a retrieved 
secret is reused before it is looked up again. 0 disables caching. Secrets 
Manager bills per API call, so a small value reduces cost, but rotated secrets 
take up to this long to be picked up.
diff --git 
a/plugins/tech/aws/src/main/resources/org/apache/hop/core/variables/resolver/aws/messages/messages_pt_PT.properties
 
b/plugins/tech/aws/src/main/resources/org/apache/hop/core/variables/resolver/aws/messages/messages_pt_PT.properties
new file mode 100644
index 0000000000..6a24e454e0
--- /dev/null
+++ 
b/plugins/tech/aws/src/main/resources/org/apache/hop/core/variables/resolver/aws/messages/messages_pt_PT.properties
@@ -0,0 +1,39 @@
+#
+# 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.
+#
+
+AwsSecretsManagerVariableResolver.Region.Label=AWS region
+AwsSecretsManagerVariableResolver.Region.Tooltip=The AWS region holding the 
secrets, for example eu-west-1. Leave empty to use AWS_REGION or the active 
profile.
+AwsSecretsManagerVariableResolver.AuthenticationType.Label=Authentication type
+AwsSecretsManagerVariableResolver.AuthenticationType.Tooltip=AUTOMATIC lets 
the AWS SDK find credentials itself (environment, profile, container or 
instance role). ACCESS_KEYS and CREDENTIALS_FILE use the options below.
+AwsSecretsManagerVariableResolver.AccessKey.Label=Access key
+AwsSecretsManagerVariableResolver.AccessKey.Tooltip=The AWS access key ID. 
Only used with authentication type ACCESS_KEYS.
+AwsSecretsManagerVariableResolver.SecretKey.Label=Secret key
+AwsSecretsManagerVariableResolver.SecretKey.Tooltip=The AWS secret access key. 
Only used with authentication type ACCESS_KEYS.
+AwsSecretsManagerVariableResolver.SessionToken.Label=Session token
+AwsSecretsManagerVariableResolver.SessionToken.Tooltip=The session token for 
temporary credentials. Optional, only used with authentication type ACCESS_KEYS.
+AwsSecretsManagerVariableResolver.CredentialsFile.Label=Credentials file
+AwsSecretsManagerVariableResolver.CredentialsFile.Tooltip=Path to an AWS 
credentials file. Only used with authentication type CREDENTIALS_FILE. Leave 
empty to use the standard location.
+AwsSecretsManagerVariableResolver.ProfileName.Label=Profile name
+AwsSecretsManagerVariableResolver.ProfileName.Tooltip=The profile to read from 
the credentials file. Defaults to "default".
+AwsSecretsManagerVariableResolver.EndpointOverride.Label=Endpoint override
+AwsSecretsManagerVariableResolver.EndpointOverride.Tooltip=An alternative 
Secrets Manager endpoint, for example a VPC endpoint or a local test service. 
Leave empty to use the regional AWS endpoint.
+AwsSecretsManagerVariableResolver.SecretNamePrefix.Label=Secret name prefix
+AwsSecretsManagerVariableResolver.SecretNamePrefix.Tooltip=Prefix placed in 
front of every secret name, for example "production/". Lets you keep 
expressions short.
+AwsSecretsManagerVariableResolver.VersionStage.Label=Version stage
+AwsSecretsManagerVariableResolver.VersionStage.Tooltip=The staging label to 
read, AWSCURRENT by default. Use AWSPREVIOUS to read the version from before 
the last rotation.
+AwsSecretsManagerVariableResolver.CacheTtlSeconds.Label=Cache TTL (seconds)
+AwsSecretsManagerVariableResolver.CacheTtlSeconds.Tooltip=How long a retrieved 
secret is reused before it is looked up again. 0 disables caching. Secrets 
Manager bills per API call, so a small value reduces cost, but rotated secrets 
take up to this long to be picked up.
diff --git 
a/plugins/tech/aws/src/main/resources/org/apache/hop/core/variables/resolver/aws/messages/messages_zh_CN.properties
 
b/plugins/tech/aws/src/main/resources/org/apache/hop/core/variables/resolver/aws/messages/messages_zh_CN.properties
new file mode 100644
index 0000000000..6a24e454e0
--- /dev/null
+++ 
b/plugins/tech/aws/src/main/resources/org/apache/hop/core/variables/resolver/aws/messages/messages_zh_CN.properties
@@ -0,0 +1,39 @@
+#
+# 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.
+#
+
+AwsSecretsManagerVariableResolver.Region.Label=AWS region
+AwsSecretsManagerVariableResolver.Region.Tooltip=The AWS region holding the 
secrets, for example eu-west-1. Leave empty to use AWS_REGION or the active 
profile.
+AwsSecretsManagerVariableResolver.AuthenticationType.Label=Authentication type
+AwsSecretsManagerVariableResolver.AuthenticationType.Tooltip=AUTOMATIC lets 
the AWS SDK find credentials itself (environment, profile, container or 
instance role). ACCESS_KEYS and CREDENTIALS_FILE use the options below.
+AwsSecretsManagerVariableResolver.AccessKey.Label=Access key
+AwsSecretsManagerVariableResolver.AccessKey.Tooltip=The AWS access key ID. 
Only used with authentication type ACCESS_KEYS.
+AwsSecretsManagerVariableResolver.SecretKey.Label=Secret key
+AwsSecretsManagerVariableResolver.SecretKey.Tooltip=The AWS secret access key. 
Only used with authentication type ACCESS_KEYS.
+AwsSecretsManagerVariableResolver.SessionToken.Label=Session token
+AwsSecretsManagerVariableResolver.SessionToken.Tooltip=The session token for 
temporary credentials. Optional, only used with authentication type ACCESS_KEYS.
+AwsSecretsManagerVariableResolver.CredentialsFile.Label=Credentials file
+AwsSecretsManagerVariableResolver.CredentialsFile.Tooltip=Path to an AWS 
credentials file. Only used with authentication type CREDENTIALS_FILE. Leave 
empty to use the standard location.
+AwsSecretsManagerVariableResolver.ProfileName.Label=Profile name
+AwsSecretsManagerVariableResolver.ProfileName.Tooltip=The profile to read from 
the credentials file. Defaults to "default".
+AwsSecretsManagerVariableResolver.EndpointOverride.Label=Endpoint override
+AwsSecretsManagerVariableResolver.EndpointOverride.Tooltip=An alternative 
Secrets Manager endpoint, for example a VPC endpoint or a local test service. 
Leave empty to use the regional AWS endpoint.
+AwsSecretsManagerVariableResolver.SecretNamePrefix.Label=Secret name prefix
+AwsSecretsManagerVariableResolver.SecretNamePrefix.Tooltip=Prefix placed in 
front of every secret name, for example "production/". Lets you keep 
expressions short.
+AwsSecretsManagerVariableResolver.VersionStage.Label=Version stage
+AwsSecretsManagerVariableResolver.VersionStage.Tooltip=The staging label to 
read, AWSCURRENT by default. Use AWSPREVIOUS to read the version from before 
the last rotation.
+AwsSecretsManagerVariableResolver.CacheTtlSeconds.Label=Cache TTL (seconds)
+AwsSecretsManagerVariableResolver.CacheTtlSeconds.Tooltip=How long a retrieved 
secret is reused before it is looked up again. 0 disables caching. Secrets 
Manager bills per API call, so a small value reduces cost, but rotated secrets 
take up to this long to be picked up.
diff --git 
a/plugins/tech/aws/src/test/java/org/apache/hop/core/variables/resolver/aws/AwsSecretsManagerVariableResolverTest.java
 
b/plugins/tech/aws/src/test/java/org/apache/hop/core/variables/resolver/aws/AwsSecretsManagerVariableResolverTest.java
new file mode 100644
index 0000000000..5080ed51b7
--- /dev/null
+++ 
b/plugins/tech/aws/src/test/java/org/apache/hop/core/variables/resolver/aws/AwsSecretsManagerVariableResolverTest.java
@@ -0,0 +1,477 @@
+/*
+ * 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.hop.core.variables.resolver.aws;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertInstanceOf;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import java.nio.charset.StandardCharsets;
+import java.util.Base64;
+import org.apache.hop.core.Const;
+import org.apache.hop.core.encryption.Encr;
+import org.apache.hop.core.encryption.TwoWayPasswordEncoderPluginType;
+import org.apache.hop.core.exception.HopException;
+import org.apache.hop.core.logging.HopLogStore;
+import org.apache.hop.core.plugins.PluginRegistry;
+import org.apache.hop.core.util.EnvUtil;
+import org.apache.hop.core.variables.IVariables;
+import org.apache.hop.core.variables.Variables;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.NullAndEmptySource;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+import software.amazon.awssdk.auth.credentials.AwsCredentials;
+import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider;
+import software.amazon.awssdk.auth.credentials.AwsSessionCredentials;
+import software.amazon.awssdk.auth.credentials.DefaultCredentialsProvider;
+import software.amazon.awssdk.auth.credentials.ProfileCredentialsProvider;
+import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider;
+import software.amazon.awssdk.core.SdkBytes;
+import software.amazon.awssdk.services.secretsmanager.SecretsManagerClient;
+import 
software.amazon.awssdk.services.secretsmanager.model.GetSecretValueRequest;
+import 
software.amazon.awssdk.services.secretsmanager.model.GetSecretValueResponse;
+import 
software.amazon.awssdk.services.secretsmanager.model.ResourceNotFoundException;
+
+/**
+ * Unit tests for the AWS Secrets Manager variable resolver. The Secrets 
Manager client is mocked so
+ * that no AWS account is needed.
+ */
+@ExtendWith(MockitoExtension.class)
+class AwsSecretsManagerVariableResolverTest {
+
+  @Mock private SecretsManagerClient mockClient;
+
+  private AwsSecretsManagerVariableResolver resolver;
+  private IVariables variables;
+
+  @BeforeAll
+  static void initHopEnvironment() throws HopException {
+    HopLogStore.init();
+    // The resolver decrypts secret keys through Encr, which needs its encoder 
plugin registered.
+    
PluginRegistry.addPluginType(TwoWayPasswordEncoderPluginType.getInstance());
+    PluginRegistry.init();
+    
Encr.init(Const.NVL(EnvUtil.getSystemProperty(Const.HOP_PASSWORD_ENCODER_PLUGIN),
 "Hop"));
+  }
+
+  @BeforeEach
+  void setUp() {
+    // Hand the resolver the mocked client instead of letting it build a real 
one.
+    resolver =
+        new AwsSecretsManagerVariableResolver() {
+          @Override
+          protected SecretsManagerClient getClient(IVariables variables) {
+            return mockClient;
+          }
+        };
+    variables = new Variables();
+  }
+
+  private void respondWithString(String value) {
+    when(mockClient.getSecretValue(any(GetSecretValueRequest.class)))
+        
.thenReturn(GetSecretValueResponse.builder().secretString(value).build());
+  }
+
+  private GetSecretValueRequest captureRequest() {
+    ArgumentCaptor<GetSecretValueRequest> captor =
+        ArgumentCaptor.forClass(GetSecretValueRequest.class);
+    verify(mockClient).getSecretValue(captor.capture());
+    return captor.getValue();
+  }
+
+  @Test
+  void testPluginMetadata() {
+    assertEquals("AwsSecretsManager", resolver.getPluginId());
+    assertEquals("AWS Secrets Manager Variable Resolver", 
resolver.getPluginName());
+  }
+
+  @Test
+  void testDefaults() {
+    assertEquals(AwsSecretsManagerAuthType.AUTOMATIC.name(), 
resolver.getAuthenticationType());
+    assertEquals("AWSCURRENT", resolver.getVersionStage());
+    assertEquals("0", resolver.getCacheTtlSeconds());
+  }
+
+  @Test
+  void testGettersAndSetters() {
+    resolver.setRegion("eu-west-1");
+    resolver.setAccessKey("AKIAEXAMPLE");
+    resolver.setSecretKey("secret");
+    resolver.setSessionToken("token");
+    resolver.setCredentialsFile("/tmp/credentials");
+    resolver.setProfileName("hop");
+    resolver.setEndpointOverride("http://localhost:4566";);
+    resolver.setSecretNamePrefix("production/");
+
+    assertEquals("eu-west-1", resolver.getRegion());
+    assertEquals("AKIAEXAMPLE", resolver.getAccessKey());
+    assertEquals("secret", resolver.getSecretKey());
+    assertEquals("token", resolver.getSessionToken());
+    assertEquals("/tmp/credentials", resolver.getCredentialsFile());
+    assertEquals("hop", resolver.getProfileName());
+    assertEquals("http://localhost:4566";, resolver.getEndpointOverride());
+    assertEquals("production/", resolver.getSecretNamePrefix());
+  }
+
+  @ParameterizedTest
+  @NullAndEmptySource
+  void testResolveWithoutSecretName(String secretName) throws Exception {
+    assertNull(resolver.resolve(secretName, variables));
+    verify(mockClient, 
never()).getSecretValue(any(GetSecretValueRequest.class));
+  }
+
+  @Test
+  void testResolveRejectsArn() throws Exception {
+    // An ARN contains colons, which the variable resolver expression syntax 
splits on. Rather than
+    // silently looking up a truncated name we refuse it.
+    String arn = 
"arn:aws:secretsmanager:eu-west-1:123456789012:secret:my-secret-AbCdEf";
+
+    assertNull(resolver.resolve(arn, variables));
+    verify(mockClient, 
never()).getSecretValue(any(GetSecretValueRequest.class));
+  }
+
+  @Test
+  void testResolveRejectsTruncatedArn() throws Exception {
+    // What an ARN actually looks like by the time it reaches the resolver: 
core splits the
+    // expression #{aws-secrets:arn:aws:secretsmanager:...} on ':' and hands 
us only "arn".
+    assertNull(resolver.resolve("arn", variables));
+    verify(mockClient, 
never()).getSecretValue(any(GetSecretValueRequest.class));
+  }
+
+  @Test
+  void testResolveRejectsArnEvenWithPrefixConfigured() throws Exception {
+    resolver.setSecretNamePrefix("production/");
+
+    assertNull(resolver.resolve("arn", variables));
+    verify(mockClient, 
never()).getSecretValue(any(GetSecretValueRequest.class));
+  }
+
+  @Test
+  void testResolveReturnsSecretString() throws Exception {
+    respondWithString("p@ssw0rd");
+
+    assertEquals("p@ssw0rd", resolver.resolve("my-secret", variables));
+    assertEquals("my-secret", captureRequest().secretId());
+  }
+
+  @Test
+  void testResolveReturnsJsonUntouched() throws Exception {
+    // Picking a single key out of the JSON is done by the variable resolver 
machinery in core, so
+    // the resolver hands back the payload as stored.
+    String json = "{\"username\":\"hop\",\"password\":\"secret\"}";
+    respondWithString(json);
+
+    assertEquals(json, resolver.resolve("db-credentials", variables));
+  }
+
+  @Test
+  void testResolveAppliesSecretNamePrefix() throws Exception {
+    resolver.setSecretNamePrefix("production/");
+    respondWithString("value");
+
+    resolver.resolve("database", variables);
+
+    assertEquals("production/database", captureRequest().secretId());
+  }
+
+  @Test
+  void testResolveResolvesVariablesInPrefix() throws Exception {
+    variables.setVariable("ENVIRONMENT", "staging");
+    resolver.setSecretNamePrefix("${ENVIRONMENT}/");
+    respondWithString("value");
+
+    resolver.resolve("database", variables);
+
+    assertEquals("staging/database", captureRequest().secretId());
+  }
+
+  @Test
+  void testResolveUsesAwsCurrentByDefault() throws Exception {
+    respondWithString("value");
+
+    resolver.resolve("my-secret", variables);
+
+    assertEquals("AWSCURRENT", captureRequest().versionStage());
+  }
+
+  @Test
+  void testResolveUsesConfiguredVersionStage() throws Exception {
+    resolver.setVersionStage("AWSPREVIOUS");
+    respondWithString("value");
+
+    resolver.resolve("my-secret", variables);
+
+    assertEquals("AWSPREVIOUS", captureRequest().versionStage());
+  }
+
+  @Test
+  void testResolveFallsBackToAwsCurrentOnEmptyVersionStage() throws Exception {
+    resolver.setVersionStage("");
+    respondWithString("value");
+
+    resolver.resolve("my-secret", variables);
+
+    assertEquals("AWSCURRENT", captureRequest().versionStage());
+  }
+
+  @Test
+  void testResolveEncodesBinarySecret() throws Exception {
+    byte[] binary = {1, 2, 3, 4, 5};
+    when(mockClient.getSecretValue(any(GetSecretValueRequest.class)))
+        .thenReturn(
+            
GetSecretValueResponse.builder().secretBinary(SdkBytes.fromByteArray(binary)).build());
+
+    assertEquals(
+        Base64.getEncoder().encodeToString(binary), 
resolver.resolve("binary-secret", variables));
+  }
+
+  @Test
+  void testResolveDecodesBinarySecretBackToOriginal() throws Exception {
+    String original = "keystore-content";
+    when(mockClient.getSecretValue(any(GetSecretValueRequest.class)))
+        .thenReturn(
+            GetSecretValueResponse.builder()
+                .secretBinary(SdkBytes.fromUtf8String(original))
+                .build());
+
+    String resolved = resolver.resolve("binary-secret", variables);
+
+    assertEquals(
+        original, new String(Base64.getDecoder().decode(resolved), 
StandardCharsets.UTF_8));
+  }
+
+  @Test
+  void testResolveReturnsNullWhenSecretNotFound() throws Exception {
+    when(mockClient.getSecretValue(any(GetSecretValueRequest.class)))
+        .thenThrow(ResourceNotFoundException.builder().message("not 
found").build());
+
+    assertNull(resolver.resolve("missing-secret", variables));
+  }
+
+  @Test
+  void testResolveReturnsNullOnFailure() throws Exception {
+    when(mockClient.getSecretValue(any(GetSecretValueRequest.class)))
+        .thenThrow(new RuntimeException("network down"));
+
+    assertNull(resolver.resolve("my-secret", variables));
+  }
+
+  @Test
+  void testCachingIsOffByDefault() throws Exception {
+    respondWithString("value");
+
+    resolver.resolve("my-secret", variables);
+    resolver.resolve("my-secret", variables);
+
+    verify(mockClient, 
times(2)).getSecretValue(any(GetSecretValueRequest.class));
+  }
+
+  @Test
+  void testCachingServesRepeatedLookupsFromCache() throws Exception {
+    resolver.setCacheTtlSeconds("60");
+    respondWithString("value");
+
+    assertEquals("value", resolver.resolve("my-secret", variables));
+    assertEquals("value", resolver.resolve("my-secret", variables));
+
+    verify(mockClient, 
times(1)).getSecretValue(any(GetSecretValueRequest.class));
+  }
+
+  @Test
+  void testCachingIsPerSecret() throws Exception {
+    resolver.setCacheTtlSeconds("60");
+    respondWithString("value");
+
+    resolver.resolve("first-secret", variables);
+    resolver.resolve("second-secret", variables);
+
+    verify(mockClient, 
times(2)).getSecretValue(any(GetSecretValueRequest.class));
+  }
+
+  @Test
+  void testExpiredCacheEntryIsLookedUpAgain() throws Exception {
+    // A TTL that has already elapsed by the time the entry is read.
+    resolver.setCacheTtlSeconds("-1");
+    respondWithString("value");
+
+    resolver.resolve("my-secret", variables);
+    resolver.resolve("my-secret", variables);
+
+    verify(mockClient, 
times(2)).getSecretValue(any(GetSecretValueRequest.class));
+  }
+
+  @Test
+  void testAutomaticAuthTypeUsesCredentialChain() throws Exception {
+    assertInstanceOf(
+        DefaultCredentialsProvider.class,
+        resolver.buildCredentialsProvider(null, null, null, null, null, null));
+  }
+
+  @Test
+  void testAccessKeysAuthTypeUsesStaticCredentials() throws Exception {
+    AwsCredentialsProvider provider =
+        resolver.buildCredentialsProvider("ACCESS_KEYS", "AKIAEXAMPLE", 
"secret", null, null, null);
+
+    assertInstanceOf(StaticCredentialsProvider.class, provider);
+    // Asserting the provider type alone would still pass with the key and the 
secret swapped.
+    AwsCredentials credentials = provider.resolveCredentials();
+    assertEquals("AKIAEXAMPLE", credentials.accessKeyId());
+    assertEquals("secret", credentials.secretAccessKey());
+  }
+
+  @Test
+  void testAccessKeysAuthTypeAcceptsSessionToken() throws Exception {
+    AwsCredentialsProvider provider =
+        resolver.buildCredentialsProvider(
+            "ACCESS_KEYS", "AKIAEXAMPLE", "secret", "session-token", null, 
null);
+
+    assertInstanceOf(StaticCredentialsProvider.class, provider);
+    AwsCredentials credentials = provider.resolveCredentials();
+    assertInstanceOf(AwsSessionCredentials.class, credentials);
+    assertEquals("AKIAEXAMPLE", credentials.accessKeyId());
+    assertEquals("secret", credentials.secretAccessKey());
+    assertEquals("session-token", ((AwsSessionCredentials) 
credentials).sessionToken());
+  }
+
+  @Test
+  void testAccessKeysAreDecryptedWhenStoredEncrypted() throws Exception {
+    // Hop's metadata serializer normally decrypts these, but a hand written 
or older metadata file
+    // can still carry the encrypted form.
+    AwsCredentialsProvider provider =
+        resolver.buildCredentialsProvider(
+            "ACCESS_KEYS",
+            "AKIAEXAMPLE",
+            Encr.encryptPasswordIfNotUsingVariables("secret"),
+            null,
+            null,
+            null);
+
+    assertEquals("secret", provider.resolveCredentials().secretAccessKey());
+  }
+
+  @Test
+  void testAccessKeysAuthTypeRequiresBothKeys() {
+    HopException e =
+        assertThrows(
+            HopException.class,
+            () ->
+                resolver.buildCredentialsProvider(
+                    "ACCESS_KEYS", "AKIAEXAMPLE", null, null, null, null));
+    assertTrue(e.getMessage().contains("ACCESS_KEYS"));
+  }
+
+  @Test
+  void testCredentialsFileAuthTypeUsesProfileProvider() throws Exception {
+    assertInstanceOf(
+        ProfileCredentialsProvider.class,
+        resolver.buildCredentialsProvider("CREDENTIALS_FILE", null, null, 
null, null, "hop"));
+  }
+
+  @Test
+  void testAuthTypeIsCaseInsensitive() throws Exception {
+    assertEquals(AwsSecretsManagerAuthType.ACCESS_KEYS, 
resolver.parseAuthType("access_keys"));
+  }
+
+  @Test
+  void testEmptyAuthTypeFallsBackToDefault() throws Exception {
+    assertEquals(AwsSecretsManagerAuthType.AUTOMATIC, 
resolver.parseAuthType(null));
+    assertEquals(AwsSecretsManagerAuthType.AUTOMATIC, 
resolver.parseAuthType(""));
+  }
+
+  @Test
+  void testUnknownAuthTypeIsRejected() {
+    HopException e = assertThrows(HopException.class, () -> 
resolver.parseAuthType("KERBEROS"));
+    assertTrue(e.getMessage().contains("KERBEROS"));
+  }
+
+  @Test
+  void testRegionComboShowsCodeAndDescription() {
+    assertTrue(resolver.getRegions(null, null).contains("eu-west-1 - Europe 
(Ireland)"));
+    // The empty entry leaves the region to the environment.
+    assertTrue(resolver.getRegions(null, null).contains(""));
+  }
+
+  @Test
+  void testRegionStoresOnlyTheCode() {
+    // Whatever the combo hands over, the field and therefore the metadata 
file keep just the code.
+    resolver.setRegion("eu-west-1 - Europe (Ireland)");
+    assertEquals("eu-west-1", resolver.getRegion());
+
+    resolver.setRegion("eu-west-1");
+    assertEquals("eu-west-1", resolver.getRegion());
+  }
+
+  @Test
+  void testRegionIsShownWithItsDescription() {
+    resolver.setRegion("eu-west-1");
+    assertEquals("eu-west-1 - Europe (Ireland)", resolver.getRegionLabel());
+  }
+
+  @Test
+  void testRegionLabelLeavesAVariableAlone() {
+    // A variable is not a region code, so there is nothing to decorate it 
with.
+    resolver.setRegion("${AWS_REGION}");
+    assertEquals("${AWS_REGION}", resolver.getRegion());
+    assertEquals("${AWS_REGION}", resolver.getRegionLabel());
+  }
+
+  @Test
+  void testRegionLabelIsEmptyWhenNoRegionIsSet() {
+    assertEquals(null, resolver.getRegionLabel());
+    resolver.setRegion("");
+    assertEquals("", resolver.getRegionLabel());
+  }
+
+  @Test
+  void testRegionLabelRoundTripsThroughTheCombo() {
+    // What the getter shows must be something the setter can take back 
without drift.
+    for (String item : resolver.getRegions(null, null)) {
+      resolver.setRegion(item);
+      assertEquals(item, resolver.getRegionLabel(), "round trip failed for 
combo entry " + item);
+    }
+  }
+
+  @Test
+  void testRegionCodeIsReadBackFromTheDisplayedValue() {
+    assertEquals(
+        "eu-west-1", AwsSecretsManagerVariableResolver.regionCode("eu-west-1 - 
Europe (Ireland)"));
+    // A hand written or variable driven value carries no description.
+    assertEquals("eu-west-1", 
AwsSecretsManagerVariableResolver.regionCode("eu-west-1"));
+    assertEquals("", AwsSecretsManagerVariableResolver.regionCode(""));
+    assertNull(AwsSecretsManagerVariableResolver.regionCode(null));
+  }
+
+  @Test
+  void testComboBoxesArePopulated() {
+    assertEquals(
+        java.util.List.of("AUTOMATIC", "ACCESS_KEYS", "CREDENTIALS_FILE"),
+        resolver.getAuthenticationTypes(null, null));
+  }
+}
diff --git 
a/plugins/tech/aws/src/test/java/org/apache/hop/core/variables/resolver/aws/AwsSecretsManagerWidgetVisibilityTest.java
 
b/plugins/tech/aws/src/test/java/org/apache/hop/core/variables/resolver/aws/AwsSecretsManagerWidgetVisibilityTest.java
new file mode 100644
index 0000000000..e935d79330
--- /dev/null
+++ 
b/plugins/tech/aws/src/test/java/org/apache/hop/core/variables/resolver/aws/AwsSecretsManagerWidgetVisibilityTest.java
@@ -0,0 +1,177 @@
+/*
+ * 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.hop.core.variables.resolver.aws;
+
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.util.function.BiConsumer;
+import java.util.function.Consumer;
+import org.apache.hop.core.gui.plugin.GuiRegistry;
+import org.apache.hop.core.variables.Variables;
+import org.apache.hop.core.variables.resolver.VariableResolver;
+import org.apache.hop.ui.core.gui.GuiCompositeWidgets;
+import org.apache.hop.ui.hopgui.HopGuiEnvironment;
+import org.apache.hop.ui.testing.SwtBotTestBase;
+import org.eclipse.swt.layout.FormLayout;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.Shell;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Tag;
+import org.junit.jupiter.api.Test;
+
+/**
+ * The three authentication types have no credentials in common, so the editor 
only shows the ones
+ * the chosen type can actually use. This checks that the others are out of 
the way, and -- just as
+ * important -- that the options which always apply stay where they are.
+ */
+@Tag("uitest")
+class AwsSecretsManagerWidgetVisibilityTest extends SwtBotTestBase {
+
+  @BeforeAll
+  static void registerGuiPluginElements() throws Exception {
+    // Puts the @GuiWidgetElement annotations of this plugin into the 
registry. Without it the
+    // composite comes up empty. The registry appends without checking for 
duplicates, so this only
+    // runs when another test class in this module has not already done it.
+    //
+    if (GuiRegistry.getInstance()
+            .findGuiElements(
+                AwsSecretsManagerVariableResolver.class.getName(),
+                VariableResolver.GUI_PLUGIN_ELEMENT_PARENT_ID)
+        == null) {
+      HopGuiEnvironment.init();
+    }
+  }
+
+  @Test
+  @DisplayName("AUTOMATIC shows no credential fields at all")
+  void automaticHidesEveryCredentialField() {
+    withWidgets(
+        resolver -> {
+          // Nothing configured: how a new resolver starts life.
+        },
+        (resolver, widgets) -> {
+          assertVisible(widgets, 
AwsSecretsManagerVariableResolver.ID_AUTHENTICATION_TYPE);
+
+          assertHidden(widgets, 
AwsSecretsManagerVariableResolver.ID_ACCESS_KEY);
+          assertHidden(widgets, 
AwsSecretsManagerVariableResolver.ID_SECRET_KEY);
+          assertHidden(widgets, 
AwsSecretsManagerVariableResolver.ID_SESSION_TOKEN);
+          assertHidden(widgets, 
AwsSecretsManagerVariableResolver.ID_CREDENTIALS_FILE);
+          assertHidden(widgets, 
AwsSecretsManagerVariableResolver.ID_PROFILE_NAME);
+        });
+  }
+
+  @Test
+  @DisplayName("ACCESS_KEYS shows the keys and not the credentials file")
+  void accessKeysShowsOnlyTheKeys() {
+    withWidgets(
+        resolver -> 
resolver.setAuthenticationType(AwsSecretsManagerAuthType.ACCESS_KEYS.name()),
+        (resolver, widgets) -> {
+          assertVisible(widgets, 
AwsSecretsManagerVariableResolver.ID_ACCESS_KEY);
+          assertVisible(widgets, 
AwsSecretsManagerVariableResolver.ID_SECRET_KEY);
+          assertVisible(widgets, 
AwsSecretsManagerVariableResolver.ID_SESSION_TOKEN);
+
+          assertHidden(widgets, 
AwsSecretsManagerVariableResolver.ID_CREDENTIALS_FILE);
+          assertHidden(widgets, 
AwsSecretsManagerVariableResolver.ID_PROFILE_NAME);
+        });
+  }
+
+  @Test
+  @DisplayName("CREDENTIALS_FILE shows the file and profile and not the keys")
+  void credentialsFileShowsOnlyTheFileFields() {
+    withWidgets(
+        resolver ->
+            
resolver.setAuthenticationType(AwsSecretsManagerAuthType.CREDENTIALS_FILE.name()),
+        (resolver, widgets) -> {
+          assertVisible(widgets, 
AwsSecretsManagerVariableResolver.ID_CREDENTIALS_FILE);
+          assertVisible(widgets, 
AwsSecretsManagerVariableResolver.ID_PROFILE_NAME);
+
+          assertHidden(widgets, 
AwsSecretsManagerVariableResolver.ID_ACCESS_KEY);
+          assertHidden(widgets, 
AwsSecretsManagerVariableResolver.ID_SECRET_KEY);
+          assertHidden(widgets, 
AwsSecretsManagerVariableResolver.ID_SESSION_TOKEN);
+        });
+  }
+
+  @Test
+  @DisplayName("the options that always apply are never hidden")
+  void generalOptionsStayVisibleForEveryAuthType() {
+    for (AwsSecretsManagerAuthType authType : 
AwsSecretsManagerAuthType.values()) {
+      withWidgets(
+          resolver -> resolver.setAuthenticationType(authType.name()),
+          (resolver, widgets) -> {
+            assertVisible(widgets, 
AwsSecretsManagerVariableResolver.ID_REGION);
+            assertVisible(widgets, 
AwsSecretsManagerVariableResolver.ID_AUTHENTICATION_TYPE);
+            assertVisible(widgets, 
AwsSecretsManagerVariableResolver.ID_ENDPOINT_OVERRIDE);
+            assertVisible(widgets, 
AwsSecretsManagerVariableResolver.ID_SECRET_NAME_PREFIX);
+            assertVisible(widgets, 
AwsSecretsManagerVariableResolver.ID_VERSION_STAGE);
+            assertVisible(widgets, 
AwsSecretsManagerVariableResolver.ID_CACHE_TTL_SECONDS);
+          });
+    }
+  }
+
+  private void withWidgets(
+      Consumer<AwsSecretsManagerVariableResolver> configure,
+      BiConsumer<AwsSecretsManagerVariableResolver, GuiCompositeWidgets> 
assertions) {
+    ensureDisplay();
+
+    Shell shell = new Shell(display);
+    shell.setLayout(new FormLayout());
+    try {
+      AwsSecretsManagerVariableResolver resolver = new 
AwsSecretsManagerVariableResolver();
+      configure.accept(resolver);
+
+      GuiCompositeWidgets widgets = new GuiCompositeWidgets(new Variables());
+      widgets.createCompositeWidgets(
+          resolver, null, shell, 
VariableResolver.GUI_PLUGIN_ELEMENT_PARENT_ID, null);
+      widgets.setWidgetsContents(resolver, shell, 
VariableResolver.GUI_PLUGIN_ELEMENT_PARENT_ID);
+      widgets.setWidgetsListener(resolver);
+      resolver.widgetsPopulated(widgets);
+
+      shell.layout(true, true);
+      shell.pack();
+
+      assertions.accept(resolver, widgets);
+    } finally {
+      if (!shell.isDisposed()) {
+        shell.dispose();
+      }
+    }
+  }
+
+  private void assertVisible(GuiCompositeWidgets widgets, String id) {
+    Control control = widgets.getWidgetsMap().get(id);
+    assertNotNull(control, "no widget registered for " + id);
+    assertTrue(control.getVisible(), id + " should be visible");
+    Control label = widgets.getLabelsMap().get(id);
+    if (label != null) {
+      assertTrue(label.getVisible(), "the label of " + id + " should be 
visible");
+    }
+  }
+
+  private void assertHidden(GuiCompositeWidgets widgets, String id) {
+    Control control = widgets.getWidgetsMap().get(id);
+    assertNotNull(control, "no widget registered for " + id);
+    assertFalse(control.getVisible(), id + " should be hidden");
+    Control label = widgets.getLabelsMap().get(id);
+    if (label != null) {
+      assertFalse(label.getVisible(), "the label of " + id + " should be 
hidden");
+    }
+  }
+}
diff --git 
a/ui/src/main/java/org/apache/hop/ui/core/variables/resolver/VariableResolverEditor.java
 
b/ui/src/main/java/org/apache/hop/ui/core/variables/resolver/VariableResolverEditor.java
index a5f028aa23..dee712743d 100644
--- 
a/ui/src/main/java/org/apache/hop/ui/core/variables/resolver/VariableResolverEditor.java
+++ 
b/ui/src/main/java/org/apache/hop/ui/core/variables/resolver/VariableResolverEditor.java
@@ -22,6 +22,7 @@ import java.util.Arrays;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Optional;
 import java.util.concurrent.atomic.AtomicBoolean;
 import org.apache.hop.core.Const;
 import org.apache.hop.core.Props;
@@ -37,6 +38,7 @@ import org.apache.hop.ui.core.dialog.ErrorDialog;
 import org.apache.hop.ui.core.gui.GuiCompositeWidgets;
 import org.apache.hop.ui.core.gui.GuiCompositeWidgetsAdapter;
 import org.apache.hop.ui.core.gui.GuiResource;
+import org.apache.hop.ui.core.gui.IGuiPluginCompositeWidgetsListener;
 import org.apache.hop.ui.core.metadata.MetadataEditor;
 import org.apache.hop.ui.core.metadata.MetadataManager;
 import org.apache.hop.ui.hopgui.HopGui;
@@ -196,14 +198,7 @@ public class VariableResolverEditor extends 
MetadataEditor<VariableResolver> {
         null);
 
     // Add listener to detect change
-    guiCompositeWidgets.setWidgetsListener(
-        new GuiCompositeWidgetsAdapter() {
-          @Override
-          public void widgetModified(
-              GuiCompositeWidgets compositeWidgets, Control changedWidget, 
String widgetId) {
-            setChanged();
-          }
-        });
+    guiCompositeWidgets.setWidgetsListener(createWidgetsListener());
 
     setWidgetsContent();
 
@@ -252,14 +247,7 @@ public class VariableResolverEditor extends 
MetadataEditor<VariableResolver> {
         wResolverSpecificComp,
         VariableResolver.GUI_PLUGIN_ELEMENT_PARENT_ID,
         null);
-    guiCompositeWidgets.setWidgetsListener(
-        new GuiCompositeWidgetsAdapter() {
-          @Override
-          public void widgetModified(
-              GuiCompositeWidgets compositeWidgets, Control changedWidget, 
String widgetId) {
-            setChanged();
-          }
-        });
+    guiCompositeWidgets.setWidgetsListener(createWidgetsListener());
 
     // Put the data back
     //
@@ -270,6 +258,50 @@ public class VariableResolverEditor extends 
MetadataEditor<VariableResolver> {
     busyChangingConnectionType.set(false);
   }
 
+  /**
+   * The composite has a single listener slot, which this editor needs for its 
own change tracking.
+   * Resolver plugins that implement {@link 
IGuiPluginCompositeWidgetsListener} -- to enable, hide
+   * or otherwise adjust their own widgets -- are forwarded to from here; 
without this their
+   * callbacks would never fire.
+   */
+  private IGuiPluginCompositeWidgetsListener createWidgetsListener() {
+    return new GuiCompositeWidgetsAdapter() {
+      @Override
+      public void widgetsCreated(GuiCompositeWidgets compositeWidgets) {
+        pluginWidgetsListener().ifPresent(listener -> 
listener.widgetsCreated(compositeWidgets));
+      }
+
+      @Override
+      public void widgetsPopulated(GuiCompositeWidgets compositeWidgets) {
+        pluginWidgetsListener().ifPresent(listener -> 
listener.widgetsPopulated(compositeWidgets));
+      }
+
+      @Override
+      public void widgetModified(
+          GuiCompositeWidgets compositeWidgets, Control changedWidget, String 
widgetId) {
+        setChanged();
+        pluginWidgetsListener()
+            .ifPresent(
+                listener -> listener.widgetModified(compositeWidgets, 
changedWidget, widgetId));
+      }
+
+      @Override
+      public void persistContents(GuiCompositeWidgets compositeWidgets) {
+        pluginWidgetsListener().ifPresent(listener -> 
listener.persistContents(compositeWidgets));
+      }
+    };
+  }
+
+  private Optional<IGuiPluginCompositeWidgetsListener> pluginWidgetsListener() 
{
+    VariableResolver resolver = getMetadata();
+    if (resolver == null) {
+      return Optional.empty();
+    }
+    return resolver.getIResolver() instanceof 
IGuiPluginCompositeWidgetsListener listener
+        ? Optional.of(listener)
+        : Optional.empty();
+  }
+
   private void enableFields() {
     // Perhaps later
   }

Reply via email to