diff --git a/change/react-native-windows-cec8f9f9-9e4b-4649-9deb-7908ecefc21e.json b/change/react-native-windows-cec8f9f9-9e4b-4649-9deb-7908ecefc21e.json
new file mode 100644
index 00000000000..d474dd2da02
--- /dev/null
+++ b/change/react-native-windows-cec8f9f9-9e4b-4649-9deb-7908ecefc21e.json
@@ -0,0 +1,7 @@
+{
+ "type": "prerelease",
+ "comment": "Add localization infrastructure for context menu strings in TextInput and Text components",
+ "packageName": "react-native-windows",
+ "email": "74712637+iamAbhi-916@users.noreply.github.com",
+ "dependentChangeType": "patch"
+}
diff --git a/vnext/Desktop.DLL/React.Windows.Desktop.DLL.vcxproj b/vnext/Desktop.DLL/React.Windows.Desktop.DLL.vcxproj
index aadb98eff59..ff8a3cc12b2 100644
--- a/vnext/Desktop.DLL/React.Windows.Desktop.DLL.vcxproj
+++ b/vnext/Desktop.DLL/React.Windows.Desktop.DLL.vcxproj
@@ -176,6 +176,7 @@
%(PreprocessorDefinitions)
+
diff --git a/vnext/Desktop/React.Windows.Desktop.vcxproj b/vnext/Desktop/React.Windows.Desktop.vcxproj
index 8b1c4f33bc9..23a426a44e5 100644
--- a/vnext/Desktop/React.Windows.Desktop.vcxproj
+++ b/vnext/Desktop/React.Windows.Desktop.vcxproj
@@ -200,6 +200,7 @@
+
diff --git a/vnext/Microsoft.ReactNative/Fabric/Composition/ParagraphComponentView.cpp b/vnext/Microsoft.ReactNative/Fabric/Composition/ParagraphComponentView.cpp
index 7974583346e..332e77f279b 100644
--- a/vnext/Microsoft.ReactNative/Fabric/Composition/ParagraphComponentView.cpp
+++ b/vnext/Microsoft.ReactNative/Fabric/Composition/ParagraphComponentView.cpp
@@ -9,7 +9,9 @@
#include
#include
+#include
#include
+#include
#include
#include
#include
@@ -829,7 +831,7 @@ void ParagraphComponentView::SetSelection(int32_t start, int32_t end) noexcept {
}
void ParagraphComponentView::ShowContextMenu() noexcept {
- HMENU menu = CreatePopupMenu();
+ std::unique_ptr, decltype(&DestroyMenu)> menu(CreatePopupMenu(), &DestroyMenu);
if (!menu) {
return;
}
@@ -838,28 +840,26 @@ void ParagraphComponentView::ShowContextMenu() noexcept {
const std::wstring utf16Text{facebook::react::WindowsTextLayoutManager::GetTransformedText(m_attributedStringBox)};
const bool hasText = !utf16Text.empty();
- // Add menu items (1 = Copy, 2 = Select All)
- AppendMenuW(menu, MF_STRING | (hasSelection ? 0 : MF_GRAYED), 1, L"Copy");
- AppendMenuW(menu, MF_STRING | (hasText ? 0 : MF_GRAYED), 2, L"Select All");
+ auto copyStr = ::Microsoft::ReactNative::GetLocalizedString(IDS_CONTEXT_MENU_COPY);
+ auto selectAllStr = ::Microsoft::ReactNative::GetLocalizedString(IDS_CONTEXT_MENU_SELECT_ALL);
+
+ AppendMenuW(menu.get(), MF_STRING | (hasSelection ? 0 : MF_GRAYED), 1, copyStr.c_str());
+ AppendMenuW(menu.get(), MF_STRING | (hasText ? 0 : MF_GRAYED), 2, selectAllStr.c_str());
- // Get cursor position for menu placement
POINT cursorPos;
GetCursorPos(&cursorPos);
const HWND hwnd = GetActiveWindow();
const int cmd = TrackPopupMenu(
- menu, TPM_LEFTALIGN | TPM_TOPALIGN | TPM_RETURNCMD | TPM_NONOTIFY, cursorPos.x, cursorPos.y, 0, hwnd, NULL);
+ menu.get(), TPM_LEFTALIGN | TPM_TOPALIGN | TPM_RETURNCMD | TPM_NONOTIFY, cursorPos.x, cursorPos.y, 0, hwnd, NULL);
if (cmd == 1) {
- // Copy
CopySelectionToClipboard();
} else if (cmd == 2) {
SetSelection(0, static_cast(utf16Text.length()));
DrawText();
}
-
- DestroyMenu(menu);
}
void ParagraphComponentView::OnKeyDown(
diff --git a/vnext/Microsoft.ReactNative/Fabric/Composition/TextInput/WindowsTextInputComponentView.cpp b/vnext/Microsoft.ReactNative/Fabric/Composition/TextInput/WindowsTextInputComponentView.cpp
index 7d181804e43..398573e8770 100644
--- a/vnext/Microsoft.ReactNative/Fabric/Composition/TextInput/WindowsTextInputComponentView.cpp
+++ b/vnext/Microsoft.ReactNative/Fabric/Composition/TextInput/WindowsTextInputComponentView.cpp
@@ -8,6 +8,8 @@
#include
#include
#include
+#include
+#include
#include
#include
#include
@@ -1900,7 +1902,7 @@ void WindowsTextInputComponentView::OnContextMenuKey(
}
void WindowsTextInputComponentView::ShowContextMenu(const winrt::Windows::Foundation::Point &position) noexcept {
- HMENU menu = CreatePopupMenu();
+ std::unique_ptr, decltype(&DestroyMenu)> menu(CreatePopupMenu(), &DestroyMenu);
if (!menu)
return;
@@ -1913,15 +1915,20 @@ void WindowsTextInputComponentView::ShowContextMenu(const winrt::Windows::Founda
bool isReadOnly = windowsTextInputProps().editable == false;
bool canPaste = !isReadOnly && IsClipboardFormatAvailable(CF_UNICODETEXT);
- AppendMenuW(menu, MF_STRING | (hasSelection && !isReadOnly ? 0 : MF_GRAYED), 1, L"Cut");
- AppendMenuW(menu, MF_STRING | (hasSelection ? 0 : MF_GRAYED), 2, L"Copy");
- AppendMenuW(menu, MF_STRING | (canPaste ? 0 : MF_GRAYED), 3, L"Paste");
- AppendMenuW(menu, MF_STRING | (!isEmpty && !isReadOnly ? 0 : MF_GRAYED), 4, L"Select All");
+ auto cutStr = ::Microsoft::ReactNative::GetLocalizedString(IDS_CONTEXT_MENU_CUT);
+ auto copyStr = ::Microsoft::ReactNative::GetLocalizedString(IDS_CONTEXT_MENU_COPY);
+ auto pasteStr = ::Microsoft::ReactNative::GetLocalizedString(IDS_CONTEXT_MENU_PASTE);
+ auto selectAllStr = ::Microsoft::ReactNative::GetLocalizedString(IDS_CONTEXT_MENU_SELECT_ALL);
+
+ AppendMenuW(menu.get(), MF_STRING | (hasSelection && !isReadOnly ? 0 : MF_GRAYED), 1, cutStr.c_str());
+ AppendMenuW(menu.get(), MF_STRING | (hasSelection ? 0 : MF_GRAYED), 2, copyStr.c_str());
+ AppendMenuW(menu.get(), MF_STRING | (canPaste ? 0 : MF_GRAYED), 3, pasteStr.c_str());
+ AppendMenuW(menu.get(), MF_STRING | (!isEmpty && !isReadOnly ? 0 : MF_GRAYED), 4, selectAllStr.c_str());
HWND hwnd = GetActiveWindow();
int cmd = TrackPopupMenu(
- menu,
+ menu.get(),
TPM_LEFTALIGN | TPM_TOPALIGN | TPM_RETURNCMD | TPM_NONOTIFY,
static_cast(position.X),
static_cast(position.Y),
@@ -1929,19 +1936,17 @@ void WindowsTextInputComponentView::ShowContextMenu(const winrt::Windows::Founda
hwnd,
NULL);
- if (cmd == 1) { // Cut
+ if (cmd == 1) {
m_textServices->TxSendMessage(WM_CUT, 0, 0, &res);
OnTextUpdated();
- } else if (cmd == 2) { // Copy
+ } else if (cmd == 2) {
m_textServices->TxSendMessage(WM_COPY, 0, 0, &res);
- } else if (cmd == 3) { // Paste
+ } else if (cmd == 3) {
m_textServices->TxSendMessage(WM_PASTE, 0, 0, &res);
OnTextUpdated();
- } else if (cmd == 4) { // Select All
+ } else if (cmd == 4) {
m_textServices->TxSendMessage(EM_SETSEL, 0, -1, &res);
}
-
- DestroyMenu(menu);
}
} // namespace winrt::Microsoft::ReactNative::Composition::implementation
diff --git a/vnext/Microsoft.ReactNative/Microsoft.ReactNative.vcxproj b/vnext/Microsoft.ReactNative/Microsoft.ReactNative.vcxproj
index 46f03bf1cbb..c2ff5e33a2c 100644
--- a/vnext/Microsoft.ReactNative/Microsoft.ReactNative.vcxproj
+++ b/vnext/Microsoft.ReactNative/Microsoft.ReactNative.vcxproj
@@ -307,6 +307,7 @@
+
@@ -372,6 +373,7 @@
+
@@ -410,6 +412,10 @@
%(PreprocessorDefinitions)
+
+
+
+
diff --git a/vnext/Microsoft.ReactNative/Microsoft.ReactNative.vcxproj.filters b/vnext/Microsoft.ReactNative/Microsoft.ReactNative.vcxproj.filters
index 44030e2c2f2..1496cbc7d51 100644
--- a/vnext/Microsoft.ReactNative/Microsoft.ReactNative.vcxproj.filters
+++ b/vnext/Microsoft.ReactNative/Microsoft.ReactNative.vcxproj.filters
@@ -143,6 +143,9 @@
Utils
+
+ Utils
+
Utils
@@ -368,6 +371,12 @@
Utils
+
+ Utils
+
+
+ Utils
+
Utils
@@ -520,6 +529,7 @@
+
diff --git a/vnext/Microsoft.ReactNative/Resources/StringResourceIds.h b/vnext/Microsoft.ReactNative/Resources/StringResourceIds.h
new file mode 100644
index 00000000000..8dbac730774
--- /dev/null
+++ b/vnext/Microsoft.ReactNative/Resources/StringResourceIds.h
@@ -0,0 +1,9 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+#pragma once
+
+#define IDS_CONTEXT_MENU_CUT 100
+#define IDS_CONTEXT_MENU_COPY 101
+#define IDS_CONTEXT_MENU_PASTE 102
+#define IDS_CONTEXT_MENU_SELECT_ALL 103
diff --git a/vnext/Microsoft.ReactNative/Resources/Strings.rc b/vnext/Microsoft.ReactNative/Resources/Strings.rc
new file mode 100644
index 00000000000..a5d61115a55
--- /dev/null
+++ b/vnext/Microsoft.ReactNative/Resources/Strings.rc
@@ -0,0 +1,12 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+#include "StringResourceIds.h"
+
+STRINGTABLE
+BEGIN
+ IDS_CONTEXT_MENU_CUT "Cut"
+ IDS_CONTEXT_MENU_COPY "Copy"
+ IDS_CONTEXT_MENU_PASTE "Paste"
+ IDS_CONTEXT_MENU_SELECT_ALL "Select All"
+END
diff --git a/vnext/Microsoft.ReactNative/Utils/LocalizedStrings.cpp b/vnext/Microsoft.ReactNative/Utils/LocalizedStrings.cpp
new file mode 100644
index 00000000000..aec93db1876
--- /dev/null
+++ b/vnext/Microsoft.ReactNative/Utils/LocalizedStrings.cpp
@@ -0,0 +1,34 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+#include "LocalizedStrings.h"
+#include
+
+namespace Microsoft::ReactNative {
+
+namespace {
+
+HMODULE GetCurrentModule() noexcept {
+ static HMODULE s_module = [] {
+ HMODULE hmod = nullptr;
+ GetModuleHandleExW(
+ GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT,
+ reinterpret_cast(&GetCurrentModule),
+ &hmod);
+ return hmod;
+ }();
+ return s_module;
+}
+
+} // namespace
+
+std::wstring GetLocalizedString(UINT stringId) noexcept {
+ LPCWSTR buffer = nullptr;
+ int length = LoadStringW(GetCurrentModule(), stringId, reinterpret_cast(&buffer), 0);
+ if (length > 0 && buffer) {
+ return std::wstring(buffer, static_cast(length));
+ }
+ return {};
+}
+
+} // namespace Microsoft::ReactNative
diff --git a/vnext/Microsoft.ReactNative/Utils/LocalizedStrings.h b/vnext/Microsoft.ReactNative/Utils/LocalizedStrings.h
new file mode 100644
index 00000000000..2ddb242bae9
--- /dev/null
+++ b/vnext/Microsoft.ReactNative/Utils/LocalizedStrings.h
@@ -0,0 +1,13 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+#pragma once
+
+#include
+#include
+
+namespace Microsoft::ReactNative {
+
+std::wstring GetLocalizedString(UINT stringId) noexcept;
+
+} // namespace Microsoft::ReactNative