diff --git a/share/qt/translate.cmake b/share/qt/translate.cmake
new file mode 100644
index 00000000000..a4eeef96038
--- /dev/null
+++ b/share/qt/translate.cmake
@@ -0,0 +1,82 @@
+# Copyright (c) 2025 The Bitcoin Core developers
+# Distributed under the MIT software license, see the accompanying
+# file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+set(input_variables
+ PROJECT_SOURCE_DIR
+ COPYRIGHT_HOLDERS
+ LCONVERT_EXECUTABLE
+ LUPDATE_EXECUTABLE
+ PYTHON_EXECUTABLE
+ XGETTEXT_EXECUTABLE
+)
+
+foreach(var IN LISTS input_variables)
+ if(NOT DEFINED ${var})
+ message(FATAL_ERROR "Variable '${var}' is not defined!")
+ endif()
+endforeach()
+
+file(GLOB_RECURSE translatable_sources
+ "${PROJECT_SOURCE_DIR}/src/*.h"
+ "${PROJECT_SOURCE_DIR}/src/*.cpp"
+ "${PROJECT_SOURCE_DIR}/src/*.mm"
+)
+
+file(GLOB_RECURSE qt_translatable_sources
+ "${PROJECT_SOURCE_DIR}/src/qt/*.h"
+ "${PROJECT_SOURCE_DIR}/src/qt/*.cpp"
+ "${PROJECT_SOURCE_DIR}/src/qt/*.mm"
+)
+
+file(GLOB ui_files
+ "${PROJECT_SOURCE_DIR}/src/qt/forms/*.ui"
+)
+
+set(subtrees crc32c crypto/ctaes leveldb minisketch secp256k1)
+set(exclude_dirs bench compat crypto support test univalue)
+foreach(directory IN LISTS subtrees exclude_dirs)
+ list(FILTER translatable_sources
+ EXCLUDE REGEX "${PROJECT_SOURCE_DIR}/src/${directory}/.*"
+ )
+endforeach()
+
+execute_process(
+ COMMAND ${CMAKE_COMMAND} -E env
+ XGETTEXT=${XGETTEXT_EXECUTABLE}
+ COPYRIGHT_HOLDERS=${COPYRIGHT_HOLDERS}
+ ${PYTHON_EXECUTABLE}
+ ${CMAKE_CURRENT_LIST_DIR}/extract_strings_qt.py
+ ${translatable_sources}
+ WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}/src
+ COMMAND_ERROR_IS_FATAL ANY
+)
+
+execute_process(
+ COMMAND ${LUPDATE_EXECUTABLE}
+ -no-obsolete
+ -I ${PROJECT_SOURCE_DIR}/src
+ -locations relative
+ ${ui_files}
+ ${qt_translatable_sources}
+ ${PROJECT_SOURCE_DIR}/src/qt/bitcoinstrings.cpp
+ -ts ${PROJECT_SOURCE_DIR}/src/qt/locale/bitcoin_en.ts
+ COMMAND_ERROR_IS_FATAL ANY
+)
+
+execute_process(
+ COMMAND ${LCONVERT_EXECUTABLE}
+ -drop-translations
+ -o ${PROJECT_SOURCE_DIR}/src/qt/locale/bitcoin_en.xlf
+ -i ${PROJECT_SOURCE_DIR}/src/qt/locale/bitcoin_en.ts
+ COMMAND_ERROR_IS_FATAL ANY
+)
+
+file(READ "${PROJECT_SOURCE_DIR}/src/qt/locale/bitcoin_en.xlf" bitcoin_en)
+string(REPLACE "source-language=\"en\" target-language=\"en\""
+ "source-language=\"en\"" bitcoin_en "${bitcoin_en}"
+)
+string(REGEX REPLACE " *\n"
+ "" bitcoin_en "${bitcoin_en}"
+)
+file(WRITE "${PROJECT_SOURCE_DIR}/src/qt/locale/bitcoin_en.xlf" "${bitcoin_en}")
diff --git a/src/qt/CMakeLists.txt b/src/qt/CMakeLists.txt
index 73645283b4e..5a3f98738f6 100644
--- a/src/qt/CMakeLists.txt
+++ b/src/qt/CMakeLists.txt
@@ -289,69 +289,19 @@ if(BUILD_GUI_TESTS)
add_subdirectory(test)
endif()
-
-# Gets sources to be parsed to gather translatable strings.
-function(get_translatable_sources var)
- set(result)
- set(targets)
- foreach(dir IN ITEMS ${ARGN})
- get_directory_property(dir_targets DIRECTORY ${PROJECT_SOURCE_DIR}/${dir} BUILDSYSTEM_TARGETS)
- list(APPEND targets ${dir_targets})
- endforeach()
- foreach(target IN LISTS targets)
- get_target_property(target_sources ${target} SOURCES)
- if(target_sources)
- foreach(source IN LISTS target_sources)
- # Get an expression from the generator expression, if any.
- if(source MATCHES ":([^>]+)>$")
- set(source ${CMAKE_MATCH_1})
- endif()
- cmake_path(GET source EXTENSION LAST_ONLY ext)
- if(ext STREQUAL ".qrc")
- continue()
- endif()
- if(NOT IS_ABSOLUTE source)
- get_target_property(target_source_dir ${target} SOURCE_DIR)
- cmake_path(APPEND target_source_dir ${source} OUTPUT_VARIABLE source)
- endif()
- get_property(is_generated
- SOURCE ${source} TARGET_DIRECTORY ${target}
- PROPERTY GENERATED
- )
- if(NOT is_generated)
- list(APPEND result ${source})
- endif()
- endforeach()
- endif()
- endforeach()
- set(${var} ${result} PARENT_SCOPE)
-endfunction()
-
find_program(XGETTEXT_EXECUTABLE xgettext)
-find_program(SED_EXECUTABLE sed)
if(NOT XGETTEXT_EXECUTABLE)
add_custom_target(translate
COMMAND ${CMAKE_COMMAND} -E echo "Error: GNU gettext-tools not found"
)
-elseif(NOT SED_EXECUTABLE)
- add_custom_target(translate
- COMMAND ${CMAKE_COMMAND} -E echo "Error: GNU sed not found"
- )
else()
- set(translatable_sources_directories src src/qt src/util)
- if(ENABLE_WALLET)
- list(APPEND translatable_sources_directories src/wallet)
- endif()
- get_translatable_sources(translatable_sources ${translatable_sources_directories})
- get_translatable_sources(qt_translatable_sources src/qt)
- file(GLOB ui_files ${CMAKE_CURRENT_SOURCE_DIR}/forms/*.ui)
- add_custom_target(translate
- COMMAND ${CMAKE_COMMAND} -E env XGETTEXT=${XGETTEXT_EXECUTABLE} COPYRIGHT_HOLDERS=${COPYRIGHT_HOLDERS} $ ${PROJECT_SOURCE_DIR}/share/qt/extract_strings_qt.py ${translatable_sources}
- COMMAND Qt6::lupdate -no-obsolete -I ${PROJECT_SOURCE_DIR}/src -locations relative ${CMAKE_CURRENT_SOURCE_DIR}/bitcoinstrings.cpp ${ui_files} ${qt_translatable_sources} -ts ${CMAKE_CURRENT_SOURCE_DIR}/locale/bitcoin_en.ts
- COMMAND Qt6::lconvert -drop-translations -o ${CMAKE_CURRENT_SOURCE_DIR}/locale/bitcoin_en.xlf -i ${CMAKE_CURRENT_SOURCE_DIR}/locale/bitcoin_en.ts
- COMMAND ${SED_EXECUTABLE} -i.old -e "s|source-language=\"en\" target-language=\"en\"|source-language=\"en\"|" -e "/<\\/target>/d" ${CMAKE_CURRENT_SOURCE_DIR}/locale/bitcoin_en.xlf
- COMMAND ${CMAKE_COMMAND} -E rm ${CMAKE_CURRENT_SOURCE_DIR}/locale/bitcoin_en.xlf.old
- WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}/src
- VERBATIM
+ add_custom_target(translate COMMAND ${CMAKE_COMMAND}
+ -D "PROJECT_SOURCE_DIR=${PROJECT_SOURCE_DIR}"
+ -D "COPYRIGHT_HOLDERS=${COPYRIGHT_HOLDERS}"
+ -D "LCONVERT_EXECUTABLE=$"
+ -D "LUPDATE_EXECUTABLE=$"
+ -D "PYTHON_EXECUTABLE=$"
+ -D "XGETTEXT_EXECUTABLE=${XGETTEXT_EXECUTABLE}"
+ -P ${PROJECT_SOURCE_DIR}/share/qt/translate.cmake
)
endif()