Skip to main content

Localization with Cognito message customization

·4 mins
CDK TypeScript Cognito Lambda

Disclaimer: It makes sense reading those articles first

Secure your React Application with Cognito & Amplify
·4 mins
CDK TypeScript Cognito React
Learn how to secure your React Application with AWS Cognito and Amplify in a step-by-step guide.
Cognito message customization
·3 mins
CDK TypeScript Cognito Lambda
Customize messages like a pro, making each interaction feel like a personal party. This guide got all the juicy tips to level up your user experience, so dive in and start customizing! ✨

Introduction
#

If you’ve read my previous articles, I’ll keep this one shorter. We’ve learned how to customize messages and add Amplify to our React app.

It’s good for users when the app speaks their language. You can use https://react.i18next.com/ to translate your app. I might write more about this later.

Now, how do we change the messages users get from Cognito?

Identifying the language
#

One method of identifying the user’s preferred language is by asking for input, while another is automating the process and retrieve the language through browser settings.

Every modern browser provides an object accessible to developers, this object is the navigator.

Specifically, we’re interested in navigator.language.

The Navigator.language read-only property returns a string representing the preferred language of the user, usually the language of the browser UI.

The string follows the RFC 5646 standard and may resemble:

  • en
  • en-US
  • en-GB
  • de
  • or any other language

The first part, such as en, defines the language, while the second part resembles a specific region, such as British English en-GB or American English en-US.

Depending on your needs, you can translate it to the general language en or a regional dialect en-GB or en-US.

For our use case, we require either English or German based on the browser settings. Here’s how we can retrieve it:

const language = navigator.language.split('-')[0]; // en, de, ...

Now we only need to forward this to cognito, so it knows which language we need.

Integration
#

1. Updating the User Pool
#

Following my previous articles, you should be able to create a Cognito User Pool with a React Amplify integration.

What you now need to do is to add locale to standardAttributes, so Cognito will persist this information on the User.

import {aws_cognito as cognito, CfnOutput, Stack, StackProps} from 'aws-cdk-lib';
import {Construct} from 'constructs';

export class CognitoStack extends Stack {
    constructor(scope: Construct, id: string, props?: StackProps) {
        super(scope, id, props);

        const customMessageLambda = new NodejsFunction(this, 'custom-message', {
            entry: 'functions/customMessage.ts',
            handler: 'handler',
        });
        
        const userPool = new cognito.UserPool(this, 'advanced-user-pool', {
            selfSignUpEnabled: true,
            standardAttributes: {
                givenName: { required: true },
                familyName: { required: true },
                locale: { required: true },
            },
            lambdaTriggers: {
                customMessage: customMessageLambda
            }
        });

        const userPoolClient = userPool.addClient('user-pool-client');

        new CfnOutput(this, 'UserPoolId', {value: userPool.userPoolId});
        new CfnOutput(this, 'UserPoolClientId', {value: userPoolClient.userPoolClientId});
    }
}
Warning! Adding required standardAttributes onto a created resource will fail.

2. Forwarding the browser language
#

Following previous articles, we might have already something similar to this:

import React from 'react';
import {Amplify} from 'aws-amplify';

import {Authenticator} from '@aws-amplify/ui-react';
import '@aws-amplify/ui-react/styles.css';

Amplify.configure({
    Auth: {
        Cognito: {
            userPoolId: 'UserPoolId from your stack output',
            userPoolClientId: 'UserPoolClientId from your stack output',
        }
    }
});

export default function App() {
    return (
        <Authenticator
            signUpAttributes={['given_name', 'family_name']}
        >
            {({signOut, user}) => (
                <main>
                    <h1>Hello {user.username}</h1>
                    <button onClick={signOut}>Sign out</button>
                </main>
            )}
        </Authenticator>
    );
}

Since we’ve decided to inject the browser language into the sign-up process, instead of letting the user select the preferred language, we need to overwrite the signUp function.

export default function App() {
    
    const handleSignUp = async (input: SignUpInput): Promise<SignUpOutput> => {
        const language = navigator.language.split('-')[0];
        input.options!.userAttributes.locale = language;
        return signUp(input);
    }

    return (
        <Authenticator
            services={{handleSignUp}}
            signUpAttributes={['given_name', 'family_name']}
        >
            {({signOut, user}) => (
                <main>
                    <h1>Hello {user.username}</h1>
                    <button onClick={signOut}>Sign out</button>
                </main>
            )}
        </Authenticator>
    );
}

Here you can learn more about overwriting function calls.

3. Updating the custom message Lambda
#

The last step is to modify the custom message Lambda, you could also add more languages if you want.

Here is my example:

import {CustomMessageTriggerHandler} from 'aws-lambda';

export const handler: CustomMessageTriggerHandler = async (event) => {
    if (event.triggerSource === 'CustomMessage_SignUp') {
        const userAttributes = event.request.userAttributes;
        const userLanguage = userAttributes.locale as 'en' | 'de';
        const userDisplayName = `${userAttributes.given_name} ${userAttributes.family_name}`;

        if (userLanguage === 'de') {
            event.response.emailSubject = 'Verifiziere deine E-Mail fΓΌr unsere tolle superluminar-App!';
            event.response.emailMessage = `<h3>Moin ${userDisplayName} πŸ‘‹</h3>
                Danke, dass du dich bei unserer tollen superluminar-App registriert hast!<br/>
                Dein Verifizierungscode ist <code>${event.request.codeParameter}</code>`;
        }

        if (userLanguage === 'en') {
            event.response.emailSubject = 'Verify your email for our awesome superluminar app!';
            event.response.emailMessage = `<h3>Hi ${userDisplayName} πŸ‘‹</h3>
                Thanks for signing up to our awesome superluminar app!<br/>
                Your verification code is <code>${event.request.codeParameter}</code>`;
        }
    }

    return event;
};

Conclusion
#

As users proceed through the sign-up process, they should receive a tailored email in their preferred language. Customizing messages boosts relevance, user experience, engagement, conversion rates, and fosters better relationships, ultimately aiming for an improved product experience πŸš€.


I hope you found value in my articles; we’ve concluded that topic for now.

Should you have any feedback, I welcome you to reach out to me through LinkedIn or email. Your input is greatly appreciated.