diff --git a/CHANGELOG.md b/CHANGELOG.md
index 4e4b3be33e..31c8279c4b 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -49,6 +49,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
- Fixed to keep telephone keypad on-screen on click, in PR [#5132](https://github.com/microsoft/BotFramework-WebChat/pull/5132)
- Disabled send button and hid message length when telephone keypad is shown, in PR [#5136](https://github.com/microsoft/BotFramework-WebChat/pull/5136)
- Added dark theme support, in PR [#5138](https://github.com/microsoft/BotFramework-WebChat/pull/5138)
+ - Added an information message to the telephone keypad, in PR [#5140](https://github.com/microsoft/BotFramework-WebChat/pull/5140)
+- (Experimental) Added `` component which can be used to localize strings, by [@OEvgeny](https://github.com/OEvgeny) in PR [#5140](https://github.com/microsoft/BotFramework-WebChat/pull/5140)
- Added `` component to apply theme pack to Web Chat, by [@compulim](https://github.com/compulim), in PR [#5120](https://github.com/microsoft/BotFramework-WebChat/pull/5120)
- Added `useMakeThumbnail` hook option to create a thumbnail from the file given, by [@compulim](https://github.com/compulim), in PR [#5123](https://github.com/microsoft/BotFramework-WebChat/pull/5123) and [#5122](https://github.com/microsoft/BotFramework-WebChat/pull/5122)
diff --git a/__tests__/__image_snapshots__/html/fluent-theme-fallback-dark-js-fluent-theme-applied-uses-fluent-dark-theme-if-present-3-snap.png b/__tests__/__image_snapshots__/html/fluent-theme-fallback-dark-js-fluent-theme-applied-uses-fluent-dark-theme-if-present-3-snap.png
index 3a386f90ac..fafb23d2a5 100644
Binary files a/__tests__/__image_snapshots__/html/fluent-theme-fallback-dark-js-fluent-theme-applied-uses-fluent-dark-theme-if-present-3-snap.png and b/__tests__/__image_snapshots__/html/fluent-theme-fallback-dark-js-fluent-theme-applied-uses-fluent-dark-theme-if-present-3-snap.png differ
diff --git a/__tests__/__image_snapshots__/html/telephone-keypad-custom-input-message-js-fluent-theme-applied-telephone-keypad-should-show-custom-message-1-snap.png b/__tests__/__image_snapshots__/html/telephone-keypad-custom-input-message-js-fluent-theme-applied-telephone-keypad-should-show-custom-message-1-snap.png
new file mode 100644
index 0000000000..60301b33cb
Binary files /dev/null and b/__tests__/__image_snapshots__/html/telephone-keypad-custom-input-message-js-fluent-theme-applied-telephone-keypad-should-show-custom-message-1-snap.png differ
diff --git a/__tests__/__image_snapshots__/html/telephone-keypad-custom-input-message-js-fluent-theme-applied-telephone-keypad-should-show-custom-message-2-snap.png b/__tests__/__image_snapshots__/html/telephone-keypad-custom-input-message-js-fluent-theme-applied-telephone-keypad-should-show-custom-message-2-snap.png
new file mode 100644
index 0000000000..bba55b77da
Binary files /dev/null and b/__tests__/__image_snapshots__/html/telephone-keypad-custom-input-message-js-fluent-theme-applied-telephone-keypad-should-show-custom-message-2-snap.png differ
diff --git a/__tests__/__image_snapshots__/html/telephone-keypad-show-hide-js-fluent-theme-applied-telephone-keypad-should-show-hide-1-snap.png b/__tests__/__image_snapshots__/html/telephone-keypad-show-hide-js-fluent-theme-applied-telephone-keypad-should-show-hide-1-snap.png
index 9460243b91..6fb9904279 100644
Binary files a/__tests__/__image_snapshots__/html/telephone-keypad-show-hide-js-fluent-theme-applied-telephone-keypad-should-show-hide-1-snap.png and b/__tests__/__image_snapshots__/html/telephone-keypad-show-hide-js-fluent-theme-applied-telephone-keypad-should-show-hide-1-snap.png differ
diff --git a/__tests__/__image_snapshots__/html/telephone-keypad-show-hide-js-fluent-theme-applied-telephone-keypad-should-show-hide-2-snap.png b/__tests__/__image_snapshots__/html/telephone-keypad-show-hide-js-fluent-theme-applied-telephone-keypad-should-show-hide-2-snap.png
index 9e405310c5..96d1694236 100644
Binary files a/__tests__/__image_snapshots__/html/telephone-keypad-show-hide-js-fluent-theme-applied-telephone-keypad-should-show-hide-2-snap.png and b/__tests__/__image_snapshots__/html/telephone-keypad-show-hide-js-fluent-theme-applied-telephone-keypad-should-show-hide-2-snap.png differ
diff --git a/__tests__/__image_snapshots__/html/telephone-keypad-tap-js-fluent-theme-applied-telephone-keypad-when-tapped-should-send-message-1-snap.png b/__tests__/__image_snapshots__/html/telephone-keypad-tap-js-fluent-theme-applied-telephone-keypad-when-tapped-should-send-message-1-snap.png
index 9408d661e5..5dd2b5b185 100644
Binary files a/__tests__/__image_snapshots__/html/telephone-keypad-tap-js-fluent-theme-applied-telephone-keypad-when-tapped-should-send-message-1-snap.png and b/__tests__/__image_snapshots__/html/telephone-keypad-tap-js-fluent-theme-applied-telephone-keypad-when-tapped-should-send-message-1-snap.png differ
diff --git a/__tests__/html/fluentTheme/telephoneKeypad.customInputMessage.html b/__tests__/html/fluentTheme/telephoneKeypad.customInputMessage.html
new file mode 100644
index 0000000000..1676d7bdd6
--- /dev/null
+++ b/__tests__/html/fluentTheme/telephoneKeypad.customInputMessage.html
@@ -0,0 +1,68 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/__tests__/html/fluentTheme/telephoneKeypad.customInputMessage.js b/__tests__/html/fluentTheme/telephoneKeypad.customInputMessage.js
new file mode 100644
index 0000000000..966dc928dd
--- /dev/null
+++ b/__tests__/html/fluentTheme/telephoneKeypad.customInputMessage.js
@@ -0,0 +1,5 @@
+/** @jest-environment ./packages/test/harness/src/host/jest/WebDriverEnvironment.js */
+
+describe('Fluent theme applied', () => {
+ test('telephone keypad should show custom message', () => runHTML('fluentTheme/telephoneKeypad.customInputMessage'));
+});
diff --git a/docs/LOCALIZATION.md b/docs/LOCALIZATION.md
index e4c88d8d0f..ffc8b9c418 100644
--- a/docs/LOCALIZATION.md
+++ b/docs/LOCALIZATION.md
@@ -261,7 +261,9 @@ function convertFromOldStringId(js) {
TEXT_INPUT_UPLOAD_BUTTON_ALT: js['Upload file'],
TEXT_INPUT_TELEPHON_KEYPAD_BUTTON_ALT: js['Telephone keypad'],
TEXT_INPUT_DROP_ZONE: js['Drop files'],
- TEXT_INPUT_LENGTH_EXCEEDED_ALT: js['Message lengths exceeded'],
+ TEXT_INPUT_LENGTH_EXCEEDED_ALT: js['Message length exceeded'],
+
+ TELEPHONE_KEYPAD_INPUT_MESSAGE: js['Only supports single-digit input'],
TEXT_INPUT_ATTACHMENTS_FEW: js['attachments'],
TEXT_INPUT_ATTACHMENTS_MANY: js['attachments'],
diff --git a/packages/api/src/localization/en-US.json b/packages/api/src/localization/en-US.json
index d815cf8baa..78950157d4 100644
--- a/packages/api/src/localization/en-US.json
+++ b/packages/api/src/localization/en-US.json
@@ -125,7 +125,8 @@
"TEXT_INPUT_UPLOAD_BUTTON_ALT": "Upload file",
"TEXT_INPUT_TELEPHONE_KEYPAD_BUTTON_ALT": "Telephone keypad",
"TEXT_INPUT_DROP_ZONE": "Drop files",
- "TEXT_INPUT_LENGTH_EXCEEDED_ALT": "Message lengths exceeded",
+ "TEXT_INPUT_LENGTH_EXCEEDED_ALT": "Message length exceeded",
+ "TELEPHONE_KEYPAD_INPUT_MESSAGE": "Only supports single-digit input",
"TEXT_INPUT_ATTACHMENTS_FEW": "$1 attachments",
"_TEXT_INPUT_ATTACHMENTS_FEW.comment": "$1 is the number of attachments. This is for plural rule of \"few\".",
"TEXT_INPUT_ATTACHMENTS_MANY": "$1 attachments",
diff --git a/packages/component/src/Utils/LocalizedString.tsx b/packages/component/src/Utils/LocalizedString.tsx
new file mode 100644
index 0000000000..517885e18e
--- /dev/null
+++ b/packages/component/src/Utils/LocalizedString.tsx
@@ -0,0 +1,143 @@
+/* eslint react/no-danger: "off" */
+
+import { hooks } from 'botframework-webchat-api';
+import { onErrorResumeNext } from 'botframework-webchat-core';
+import MarkdownIt from 'markdown-it';
+import React, { memo, useMemo } from 'react';
+import betterLinks, { type BetterLinkEnv, type LinkOptions } from './betterLinks';
+
+const allowedSchemes = ['data', 'http', 'https', 'ftp', 'mailto', 'sip', 'tel'];
+
+const linkDefinitions = [];
+
+const externalLinkAlt = '';
+
+const defaultDecorateLink = (href: string, textContent: string, linkOptions?: LinkOptions): LinkOptions | undefined => {
+ const decoration: LinkOptions = {
+ rel: 'noopener noreferrer',
+ target: '_blank',
+ wrapZeroWidthSpace: true,
+ ...linkOptions
+ };
+
+ const ariaLabelSegments: string[] = [textContent];
+ const classes: Set = new Set();
+ const linkDefinition = linkDefinitions.find(({ url }) => url === href);
+ const protocol = onErrorResumeNext(() => new URL(href).protocol);
+
+ if (linkDefinition) {
+ ariaLabelSegments.push(
+ linkDefinition.title || onErrorResumeNext(() => new URL(linkDefinition.url).host) || linkDefinition.url
+ );
+
+ // linkDefinition.identifier is uppercase, while linkDefinition.label is as-is.
+ linkDefinition.label === textContent && classes.add('webchat__render-markdown__pure-identifier');
+ }
+
+ // For links that would be sanitized out, let's turn them into a button so we could handle them later.
+ if (!allowedSchemes.map(scheme => `${scheme}:`).includes(protocol)) {
+ decoration.asButton ??= true;
+
+ classes.add('webchat__render-markdown__citation');
+ } else if (protocol === 'http:' || protocol === 'https:') {
+ decoration.iconClassName = [decoration.iconClassName, 'webchat__render-markdown__external-link-icon']
+ .filter((className: string | undefined) => className)
+ .join(' ');
+
+ ariaLabelSegments.push(externalLinkAlt);
+ }
+
+ // The first segment is textContent. Putting textContent is aria-label is useless.
+ if (ariaLabelSegments.length > 1) {
+ // If "aria-label" is already applied, do not overwrite it.
+ decoration.ariaLabel ??= (value: string) => value || ariaLabelSegments.join(' ');
+ }
+
+ if (typeof linkOptions?.className === 'string') {
+ classes.add(linkOptions.className);
+ }
+
+ // Resolve className
+ const classNamesString = Array.from(classes).join(' ');
+ if (linkOptions?.className && linkOptions?.className instanceof Function) {
+ decoration.className = linkOptions.className(classNamesString);
+ } else {
+ decoration.className = classNamesString;
+ }
+
+ // By default, Markdown-It will set "title" to the link title in link definition.
+
+ // However, "title" may be narrated by screen reader:
+ // - Edge
+ // - will narrate "aria-label" but not "title"
+ // -