1
0
Fork 0
mirror of https://github.com/EdgeTX/edgetx.git synced 2025-07-26 01:35:16 +03:00

chore: make helper tools more robust, translation helpers (#6396)

This commit is contained in:
Peter Feerick 2025-07-01 12:54:00 +10:00 committed by GitHub
parent c31b54041b
commit 112df45521
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 645 additions and 113 deletions

View file

@ -1,14 +1,35 @@
#!/bin/bash
LVGLDIR="../../thirdparty/lvgl"
TTF_DIR="../"
# Exit on any error, undefined variables, or pipe failures
set -euo pipefail
SYMBOLS_FONT="${LVGLDIR}/scripts/built_in_font/FontAwesome5-Solid+Brands+Regular.woff"
# Get the directory where this script is located
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
RADIO_SRC_DIR="$(cd "${SCRIPT_DIR}/../.." && pwd)"
TRANSLATIONS_DIR="${RADIO_SRC_DIR}/translations"
SYMBOLS_FONT="${RADIO_SRC_DIR}/thirdparty/lvgl/scripts/built_in_font/FontAwesome5-Solid+Brands+Regular.woff"
# test if realpath supports --relative-to as macOS BSD version doesn't
if realpath --relative-to="." "." >/dev/null 2>&1; then
# GNU realpath with --relative-to support
SYMBOLS_FONT_REL="$(realpath --relative-to="${SCRIPT_DIR}" "${SYMBOLS_FONT}")"
else
# BSD realpath or no realpath - use hardcoded relative path
SYMBOLS_FONT_REL="../../thirdparty/lvgl/scripts/built_in_font/FontAwesome5-Solid+Brands+Regular.woff"
fi
SYMBOLS="61441,61448,61451,61452,61453,61457,61459,61461,61465,61468,61473,61478,61479,61480,61502,61507,61512,61515,61516,61517,61521,61522,61523,61524,61543,61544,61550,61552,61553,61556,61559,61560,61561,61563,61587,61589,61636,61637,61639,61641,61664,61671,61674,61683,61724,61732,61787,61931,62016,62017,62018,62019,62020,62087,62099,62212,62189,62810,63426,63650"
EXTRA_FONT="EdgeTX/extra.ttf"
EXTRA_SYM="0x88-0x96"
# https://yeun.github.io/open-arrow/
ARROWS_FONT="EdgeTX/OpenArrow-Regular.woff"
# 0x80: right, 0x81: left, 0x82: up, 0x83: down
ARROWS="0x21E8=>0x80,0x21E6=>0x81,0x21E7=>0x82,0x21E9=>0x83"
LATIN_FONT="Roboto/Roboto-Regular.ttf"
LATIN_FONT_BOLD="Roboto/Roboto-Bold.ttf"
ASCII="0x20-0x7F"
DEGREE="0xB0"
BULLET="0x2022"
@ -17,21 +38,38 @@ LATIN1_EXT_A="0x100-0x17F"
LATIN1="${LATIN1_SUPPLEMENT},${LATIN1_EXT_A}"
COMPARE="0x2265"
TW_SYMBOLS=$(python3 get_char_ck.py ../../translations/tw.h)
CN_SYMBOLS=$(python3 get_char_ck.py ../../translations/cn.h)
JP_SYMBOLS=$(python3 get_char_jp.py ../../translations/jp.h)
HE_SYMBOLS=$(python3 get_char_he.py ../../translations/he.h)
KO_SYMBOLS=$(python3 get_char_ko.py ../../translations/ko.h)
RU_SYMBOLS=$(python3 get_char_cyrillic.py ../../translations/ru.h)
UA_SYMBOLS=$(python3 get_char_cyrillic.py ../../translations/ua.h)
# LV_SYMBOL_CHARGE, LV_SYMBOL_NEW_LINE, LV_SYMBOL_SD_CARD, LV_SYMBOL_CLOSE
# LV_SYMBOL_FILE, LV_SYMBOL_OK, LV_SYMBOL_WIFI, LV_SYMBOL_USB
BL_SYMBOLS="61671,63650,63426,61453,61787,61452,61931,62087"
# https://yeun.github.io/open-arrow/
ARROWS_FONT="EdgeTX/OpenArrow-Regular.woff"
# 0x80: right, 0x81: left, 0x82: up, 0x83: down
ARROWS="0x21E8=>0x80,0x21E6=>0x81,0x21E7=>0x82,0x21E9=>0x83"
check_dependencies() {
# Check if lv_font_conv is available
if ! command -v lv_font_conv >/dev/null 2>&1; then
echo "ERROR: lv_font_conv not found. Please install it from https://github.com/lvgl/lv_font_conv or npm registry" >&2
exit 1
fi
LATIN_FONT="Roboto/Roboto-Regular.ttf"
LATIN_FONT_BOLD="Roboto/Roboto-Bold.ttf"
# Check if we're on macOS and provide helpful info about realpath
if [[ "$(uname)" == "Darwin" ]]; then
if ! realpath --relative-to="." "." >/dev/null 2>&1; then
echo "INFO: Using BSD realpath (no --relative-to support). Using hardcoded relative paths." >&2
echo "INFO: To install GNU coreutils (optional): brew install coreutils" >&2
fi
fi
}
get_translation_symbols() {
TW_SYMBOLS=$(python3 get_char_ck.py "${TRANSLATIONS_DIR}/tw.h" 2>/dev/null || echo "")
CN_SYMBOLS=$(python3 get_char_ck.py "${TRANSLATIONS_DIR}/cn.h" 2>/dev/null || echo "")
JP_SYMBOLS=$(python3 get_char_jp.py "${TRANSLATIONS_DIR}/jp.h" 2>/dev/null || echo "")
HE_SYMBOLS=$(python3 get_char_he.py "${TRANSLATIONS_DIR}/he.h" 2>/dev/null || echo "")
KO_SYMBOLS=$(python3 get_char_ko.py "${TRANSLATIONS_DIR}/ko.h" 2>/dev/null || echo "")
RU_SYMBOLS=$(python3 get_char_cyrillic.py "${TRANSLATIONS_DIR}/ru.h" 2>/dev/null || echo "")
UA_SYMBOLS=$(python3 get_char_cyrillic.py "${TRANSLATIONS_DIR}/ua.h" 2>/dev/null || echo "")
# Export variables for later use
export TW_SYMBOLS CN_SYMBOLS JP_SYMBOLS HE_SYMBOLS KO_SYMBOLS RU_SYMBOLS UA_SYMBOLS
}
function make_font() {
local name=$1
@ -42,20 +80,29 @@ function make_font() {
local dir=$6
local chars=$7
lv_font_conv --no-prefilter --bpp 4 --size ${size} \
--font ${TTF_DIR}${latin_ttf} -r ${ASCII},${DEGREE},${BULLET},${COMPARE} \
--font ${TTF_DIR}${ttf} -r ${chars} \
--font EdgeTX/extra.ttf -r ${EXTRA_SYM} \
--font ${ARROWS_FONT} -r ${ARROWS} \
--font ${SYMBOLS_FONT} -r ${SYMBOLS} \
--format lvgl -o ${dir}/lv_font_${name}_${sfx}.c --force-fast-kern-format --no-compress
echo "Creating font: ${name}_${sfx} (size: ${size})"
# Use relative paths for lv_font_conv to avoid absolute paths in generated files
lv_font_conv --no-prefilter --bpp 4 --size "${size}" \
--font "../${latin_ttf}" -r "${ASCII},${DEGREE},${BULLET},${COMPARE}" \
--font "../${ttf}" -r "${chars}" \
--font "${EXTRA_FONT}" -r "${EXTRA_SYM}" \
--font "${ARROWS_FONT}" -r "${ARROWS}" \
--font "${SYMBOLS_FONT_REL}" -r "${SYMBOLS}" \
--format lvgl -o "${dir}/lv_font_${name}_${sfx}.c" --force-fast-kern-format --no-compress
}
function compress_font() {
local name=$1
gcc -I ../../thirdparty lz4_font.cpp ../../thirdparty/lz4/lz4hc.c ../../thirdparty/lz4/lz4.c -o lz4_font
./lz4_font ${name}
# Compile the compression tool
gcc -I "${RADIO_SRC_DIR}/thirdparty" \
"${SCRIPT_DIR}/lz4_font.cpp" \
"${RADIO_SRC_DIR}/thirdparty/lz4/lz4hc.c" \
"${RADIO_SRC_DIR}/thirdparty/lz4/lz4.c" \
-o "${SCRIPT_DIR}/lz4_font"
"${SCRIPT_DIR}/lz4_font" "${name}"
}
function make_font_lz4() {
@ -67,14 +114,17 @@ function make_font_lz4() {
local dir=$6
local chars=$7
lv_font_conv --no-prefilter --bpp 4 --size ${size} \
--font ${TTF_DIR}${latin_ttf} -r ${ASCII},${DEGREE},${BULLET},${COMPARE} \
--font ${TTF_DIR}${ttf} -r ${chars} \
--font EdgeTX/extra.ttf -r ${EXTRA_SYM} \
--font ${ARROWS_FONT} -r ${ARROWS} \
--font ${SYMBOLS_FONT} -r ${SYMBOLS} \
--format lvgl -o lv_font.inc --force-fast-kern-format --no-compress
compress_font ${dir}/lv_font_${name}_${sfx}
echo "Creating compressed font: ${name}_${sfx} (size: ${size})"
# Use relative paths for lv_font_conv
lv_font_conv --no-prefilter --bpp 4 --size "${size}" \
--font "../${latin_ttf}" -r "${ASCII},${DEGREE},${BULLET},${COMPARE}" \
--font "../${ttf}" -r "${chars}" \
--font "${EXTRA_FONT}" -r "${EXTRA_SYM}" \
--font "${ARROWS_FONT}" -r "${ARROWS}" \
--font "${SYMBOLS_FONT_REL}" -r "${SYMBOLS}" \
--format lvgl -o "lv_font.inc" --force-fast-kern-format --no-compress
compress_font "${dir}/lv_font_${name}_${sfx}"
}
function make_font_w_extra_sym() {
@ -86,12 +136,14 @@ function make_font_w_extra_sym() {
local dir=$6
local chars=$7
lv_font_conv --no-prefilter --bpp 4 --size ${size} \
--font ${TTF_DIR}${latin_ttf} -r ${ASCII},${DEGREE} \
--font ${TTF_DIR}${ttf} -r ${chars} \
--font EdgeTX/extra.ttf -r ${EXTRA_SYM} \
--format lvgl -o lv_font.inc --force-fast-kern-format --no-compress
compress_font ${dir}/lv_font_${name}_${sfx}
echo "Creating font with extra symbols: ${name}_${sfx} (size: ${size})"
lv_font_conv --no-prefilter --bpp 4 --size "${size}" \
--font "../${latin_ttf}" -r "${ASCII},${DEGREE}" \
--font "../${ttf}" -r "${chars}" \
--font "${EXTRA_FONT}" -r "${EXTRA_SYM}" \
--format lvgl -o "lv_font.inc" --force-fast-kern-format --no-compress
compress_font "${dir}/lv_font_${name}_${sfx}"
}
function make_font_no_sym() {
@ -103,11 +155,13 @@ function make_font_no_sym() {
local dir=$6
local chars=$7
lv_font_conv --no-prefilter --bpp 4 --size ${size} \
--font ${TTF_DIR}${latin_ttf} -r ${ASCII},${DEGREE} \
--font ${TTF_DIR}${ttf} -r ${chars} \
--format lvgl -o lv_font.inc --force-fast-kern-format --no-compress
compress_font ${dir}/lv_font_${name}_${sfx}
echo "Creating font without symbols: ${name}_${sfx} (size: ${size})"
lv_font_conv --no-prefilter --bpp 4 --size "${size}" \
--font "../${latin_ttf}" -r "${ASCII},${DEGREE}" \
--font "../${ttf}" -r "${chars}" \
--format lvgl -o "lv_font.inc" --force-fast-kern-format --no-compress
compress_font "${dir}/lv_font_${name}_${sfx}"
}
function make_font_no_sym_no_trans() {
@ -117,15 +171,13 @@ function make_font_no_sym_no_trans() {
local sfx=$4
local dir=$5
lv_font_conv --no-prefilter --bpp 4 --size ${size} \
--font ${TTF_DIR}${latin_ttf} -r ${ASCII},${DEGREE} \
--format lvgl -o lv_font.inc --force-fast-kern-format --no-compress
compress_font ${dir}/lv_font_${name}_${sfx}
}
echo "Creating basic font: ${name}_${sfx} (size: ${size})"
# LV_SYMBOL_CHARGE, LV_SYMBOL_NEW_LINE, LV_SYMBOL_SD_CARD, LV_SYMBOL_CLOSE
# LV_SYMBOL_FILE, LV_SYMBOL_OK, LV_SYMBOL_WIFI, LV_SYMBOL_USB
BL_SYMBOLS="61671,63650,63426,61453,61787,61452,61931,62087"
lv_font_conv --no-prefilter --bpp 4 --size "${size}" \
--font "../${latin_ttf}" -r "${ASCII},${DEGREE}" \
--format lvgl -o "lv_font.inc" --force-fast-kern-format --no-compress
compress_font "${dir}/lv_font_${name}_${sfx}"
}
function make_bootloader_font() {
local name=$1
@ -133,10 +185,12 @@ function make_bootloader_font() {
local size=$3
local dir=$4
lv_font_conv --no-prefilter --bpp 1 --size ${size} --no-compress \
--font ${TTF_DIR}${ttf} -r ${ASCII} \
--font ${SYMBOLS_FONT} -r ${BL_SYMBOLS} \
--format lvgl -o ${dir}/lv_font_${name}.c --force-fast-kern-format --no-compress
echo "Creating bootloader font: ${name} (size: ${size})"
lv_font_conv --no-prefilter --bpp 1 --size "${size}" --no-compress \
--font "../${ttf}" -r "${ASCII}" \
--font "${SYMBOLS_FONT_REL}" -r "${BL_SYMBOLS}" \
--format lvgl -o "${dir}/lv_font_${name}.c" --force-fast-kern-format --no-compress
}
function make_font_set() {
@ -145,49 +199,79 @@ function make_font_set() {
local ttf_bold=$3
local chars=$4
make_font_lz4 "${name}" ${LATIN_FONT} "${ttf_normal}" 9 "XXS" "std" ${chars}
make_font_lz4 "${name}" ${LATIN_FONT} "${ttf_normal}" 13 "XS" "std" ${chars}
make_font "${name}" ${LATIN_FONT} "${ttf_normal}" 16 "STD" "std" ${chars}
make_font_lz4 "${name}_bold" ${LATIN_FONT_BOLD} "${ttf_bold}" 16 "STD" "std" ${chars}
make_font_w_extra_sym "${name}" ${LATIN_FONT} "${ttf_normal}" 24 "L" "std" ${chars}
make_font_no_sym "${name}_bold" ${LATIN_FONT_BOLD} "${ttf_bold}" 32 "XL" "std" ${chars}
if [[ -z "$chars" ]]; then
echo "WARNING: No characters found for ${name} font set. Skipping." >&2
return 0
fi
# 320x240 LCD fonts
make_font_lz4 "${name}" ${LATIN_FONT} "${ttf_normal}" 8 "XXS" "sml" ${chars}
make_font_lz4 "${name}" ${LATIN_FONT} "${ttf_normal}" 10 "XS" "sml" ${chars}
make_font "${name}" ${LATIN_FONT} "${ttf_normal}" 13 "STD" "sml" ${chars}
make_font_lz4 "${name}_bold" ${LATIN_FONT_BOLD} "${ttf_bold}" 13 "STD" "sml" ${chars}
make_font_w_extra_sym "${name}" ${LATIN_FONT} "${ttf_normal}" 19 "L" "sml" ${chars}
make_font_no_sym "${name}_bold" ${LATIN_FONT_BOLD} "${ttf_bold}" 25 "XL" "sml" ${chars}
echo "Creating font set for: ${name}"
# 800x480 LCD fonts
make_font_lz4 "${name}" ${LATIN_FONT} "${ttf_normal}" 13 "XXS" "lrg" ${chars}
make_font_lz4 "${name}" ${LATIN_FONT} "${ttf_normal}" 19 "XS" "lrg" ${chars}
make_font "${name}" ${LATIN_FONT} "${ttf_normal}" 24 "STD" "lrg" ${chars}
make_font_lz4 "${name}_bold" ${LATIN_FONT_BOLD} "${ttf_bold}" 24 "STD" "lrg" ${chars}
make_font_w_extra_sym "${name}" ${LATIN_FONT} "${ttf_normal}" 36 "L" "lrg" ${chars}
make_font_no_sym "${name}_bold" ${LATIN_FONT_BOLD} "${ttf_bold}" 48 "XL" "lrg" ${chars}
# Standard LCD fonts (480x272, 480x320, 320x480)
make_font_lz4 "${name}" "${LATIN_FONT}" "${ttf_normal}" 9 "XXS" "std" "${chars}"
make_font_lz4 "${name}" "${LATIN_FONT}" "${ttf_normal}" 13 "XS" "std" "${chars}"
make_font "${name}" "${LATIN_FONT}" "${ttf_normal}" 16 "STD" "std" "${chars}"
make_font_lz4 "${name}_bold" "${LATIN_FONT_BOLD}" "${ttf_bold}" 16 "STD" "std" "${chars}"
make_font_w_extra_sym "${name}" "${LATIN_FONT}" "${ttf_normal}" 24 "L" "std" "${chars}"
make_font_no_sym "${name}_bold" "${LATIN_FONT_BOLD}" "${ttf_bold}" 32 "XL" "std" "${chars}"
# Small LCD fonts (320x240)
make_font_lz4 "${name}" "${LATIN_FONT}" "${ttf_normal}" 8 "XXS" "sml" "${chars}"
make_font_lz4 "${name}" "${LATIN_FONT}" "${ttf_normal}" 10 "XS" "sml" "${chars}"
make_font "${name}" "${LATIN_FONT}" "${ttf_normal}" 13 "STD" "sml" "${chars}"
make_font_lz4 "${name}_bold" "${LATIN_FONT_BOLD}" "${ttf_bold}" 13 "STD" "sml" "${chars}"
make_font_w_extra_sym "${name}" "${LATIN_FONT}" "${ttf_normal}" 19 "L" "sml" "${chars}"
make_font_no_sym "${name}_bold" "${LATIN_FONT_BOLD}" "${ttf_bold}" 25 "XL" "sml" "${chars}"
# Large LCD fonts (800x480)
make_font_lz4 "${name}" "${LATIN_FONT}" "${ttf_normal}" 13 "XXS" "lrg" "${chars}"
make_font_lz4 "${name}" "${LATIN_FONT}" "${ttf_normal}" 19 "XS" "lrg" "${chars}"
make_font "${name}" "${LATIN_FONT}" "${ttf_normal}" 24 "STD" "lrg" "${chars}"
make_font_lz4 "${name}_bold" "${LATIN_FONT_BOLD}" "${ttf_bold}" 24 "STD" "lrg" "${chars}"
make_font_w_extra_sym "${name}" "${LATIN_FONT}" "${ttf_normal}" 36 "L" "lrg" "${chars}"
make_font_no_sym "${name}_bold" "${LATIN_FONT_BOLD}" "${ttf_bold}" 48 "XL" "lrg" "${chars}"
}
# Bootloader font
make_bootloader_font "bl" "Roboto/Roboto-Regular-BL.ttf" 16 "std" # 480x272, 480x320, 320x480
make_bootloader_font "bl" "Roboto/Roboto-Regular-BL.ttf" 14 "sml" # 320x240
make_bootloader_font "bl" "Roboto/Roboto-Regular-BL.ttf" 24 "lrg" # 800x480
# Main execution starts here
main() {
echo "Starting font generation..."
# XXL fonts (no translation chars)
make_font_no_sym_no_trans "en_bold" ${LATIN_FONT_BOLD} 64 "XXL" "std"
make_font_no_sym_no_trans "en_bold" ${LATIN_FONT_BOLD} 50 "XXL" "sml"
make_font_no_sym_no_trans "en_bold" ${LATIN_FONT_BOLD} 96 "XXL" "lrg"
# Change to script directory for python and other scripts to work correctly
cd "${SCRIPT_DIR}"
# Language fonts
make_font_set "en" ${LATIN_FONT} ${LATIN_FONT_BOLD} "${LATIN1}"
make_font_set "tw" "Noto/NotoSansCJKsc-Regular.otf" "Noto/NotoSansCJKsc-Bold.otf" "${TW_SYMBOLS}"
make_font_set "cn" "Noto/NotoSansCJKsc-Regular.otf" "Noto/NotoSansCJKsc-Bold.otf" "${CN_SYMBOLS}"
make_font_set "jp" "Noto/NotoSansCJKsc-Regular.otf" "Noto/NotoSansCJKsc-Bold.otf" "${JP_SYMBOLS}"
make_font_set "he" "Arimo/Arimo-Regular.ttf" "Arimo/Arimo-Bold.ttf" "${HE_SYMBOLS}"
make_font_set "ru" "Arimo/Arimo-Regular.ttf" "Arimo/Arimo-Bold.ttf" "${RU_SYMBOLS}"
make_font_set "ua" "Arimo/Arimo-Regular.ttf" "Arimo/Arimo-Bold.ttf" "${UA_SYMBOLS}"
make_font_set "ko" "Nanum/NanumBarunpenR.ttf" "Nanum/NanumBarunpenB.ttf" "${KO_SYMBOLS}"
# Check dependencies and setup
check_dependencies
get_translation_symbols
rm lv_font.inc
rm lz4_font
# Bootloader fonts
echo "Generating bootloader fonts..."
make_bootloader_font "bl" "Roboto/Roboto-Regular-BL.ttf" 16 "std" # 480x272, 480x320, 320x480
make_bootloader_font "bl" "Roboto/Roboto-Regular-BL.ttf" 14 "sml" # 320x240
make_bootloader_font "bl" "Roboto/Roboto-Regular-BL.ttf" 24 "lrg" # 800x480
# XXL fonts (no translation chars)
echo "Generating XXL fonts..."
make_font_no_sym_no_trans "en_bold" "${LATIN_FONT_BOLD}" 64 "XXL" "std"
make_font_no_sym_no_trans "en_bold" "${LATIN_FONT_BOLD}" 50 "XXL" "sml"
make_font_no_sym_no_trans "en_bold" "${LATIN_FONT_BOLD}" 96 "XXL" "lrg"
# Language fonts
echo "Generating language font sets..."
make_font_set "en" "${LATIN_FONT}" "${LATIN_FONT_BOLD}" "${LATIN1}"
make_font_set "tw" "Noto/NotoSansCJKsc-Regular.otf" "Noto/NotoSansCJKsc-Bold.otf" "${TW_SYMBOLS}"
make_font_set "cn" "Noto/NotoSansCJKsc-Regular.otf" "Noto/NotoSansCJKsc-Bold.otf" "${CN_SYMBOLS}"
make_font_set "jp" "Noto/NotoSansCJKsc-Regular.otf" "Noto/NotoSansCJKsc-Bold.otf" "${JP_SYMBOLS}"
make_font_set "he" "Arimo/Arimo-Regular.ttf" "Arimo/Arimo-Bold.ttf" "${HE_SYMBOLS}"
make_font_set "ru" "Arimo/Arimo-Regular.ttf" "Arimo/Arimo-Bold.ttf" "${RU_SYMBOLS}"
make_font_set "ua" "Arimo/Arimo-Regular.ttf" "Arimo/Arimo-Bold.ttf" "${UA_SYMBOLS}"
make_font_set "ko" "Nanum/NanumBarunpenR.ttf" "Nanum/NanumBarunpenB.ttf" "${KO_SYMBOLS}"
# Clean up temporary files
echo "Cleaning up temporary files..."
rm -f "${SCRIPT_DIR}/lv_font.inc"
rm -f "${SCRIPT_DIR}/lz4_font"
echo "Font generation completed successfully!"
}
# Run main function
main "$@"

View file

@ -288,6 +288,7 @@
#define TR_SWITCH "Przełą"
#define TR_FUNCTION_SWITCHES "Ustawiane przełączniki"
#define TR_FS_COLOR_LIST "Custom","Off","White","Red","Green","Yellow","Orange","Blue","Pink"
#define TR_GROUP "Group"
#define TR_GROUP_ALWAYS_ON "Always on"
#define TR_FS_ON_COLOR TR("ON:","ON Color")
#define TR_FS_OFF_COLOR TR("OFF:","OFF Color")

View file

@ -29,6 +29,18 @@ set -e
# zh_TW.UTF-8 / zh_TW.utf8
# uk_UA.UTF-8 / uk_UA.utf8
# Get the directory where this script is located and the project root
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
PROJECT_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)"
# Define paths relative to project root
TOOLS_DIR="${PROJECT_ROOT}/tools"
RADIO_SRC_DIR="${PROJECT_ROOT}/radio/src"
CFN_SORTER_CPP="${TOOLS_DIR}/cfn_sorter.cpp"
COPYRIGHT_HEADER="${TOOLS_DIR}/copyright-header.txt"
OUTPUT_FILE="${RADIO_SRC_DIR}/cfn_sort.cpp"
EXECUTABLE="${SCRIPT_DIR}/a.out"
# Determine the compiler to use
if command -v g++ >/dev/null 2>&1; then
CXX=g++
@ -49,38 +61,40 @@ compile_and_append() {
echo "Compiling ${translation_macro} (${lng_macro}) ..."
$CXX -std=c++11 -lstdc++ -Wfatal-errors -D${lng_macro} tools/cfn_sorter.cpp
# Change to script directory for compilation
cd "${SCRIPT_DIR}"
$CXX -std=c++11 -lstdc++ -Wfatal-errors -D${lng_macro} "${CFN_SORTER_CPP}"
{
if [ "$condition" = "else" ]; then
echo "#${condition}"
else
echo "#${condition} defined(${translation_macro})"
fi
./a.out
} >> radio/src/cfn_sort.cpp
"${EXECUTABLE}"
} >> "${OUTPUT_FILE}"
}
rm radio/src/cfn_sort.cpp
# Ensure output directory exists
mkdir -p "${RADIO_SRC_DIR}"
# Copyright header
cat tools/copyright-header.txt > radio/src/cfn_sort.cpp
# Remove existing output file
rm -f "${OUTPUT_FILE}"
# Start of cfn_sort.cpp
echo "Compiling TRANSLATIONS_CN (LNG_CN) ..."
$CXX -std=c++11 -lstdc++ -Wfatal-errors -DLNG_CN tools/cfn_sorter.cpp
{
cat <<EOF >> radio/src/cfn_sort.cpp
# Start the file with boilerplate copyright header
cat "${COPYRIGHT_HEADER}" > "${OUTPUT_FILE}"
# Add the rest of the file header
cat <<EOF >> "${OUTPUT_FILE}"
// This file is auto-generated via cfn_sorter.sh. Do not edit.
#include "dataconstants.h"
Functions cfn_sorted[] = {
#if defined(TRANSLATIONS_CN)
EOF
./a.out
} >> radio/src/cfn_sort.cpp
# Languages
# Languages - compile and append each translation
compile_and_append "LNG_CN" "TRANSLATIONS_CN" "if"
compile_and_append "LNG_CZ" "TRANSLATIONS_CZ" "elif"
compile_and_append "LNG_DA" "TRANSLATIONS_DA" "elif"
compile_and_append "LNG_DE" "TRANSLATIONS_DE" "elif"
@ -99,11 +113,10 @@ compile_and_append "LNG_SE" "TRANSLATIONS_SE" "elif"
compile_and_append "LNG_TW" "TRANSLATIONS_TW" "elif"
compile_and_append "LNG_UA" "TRANSLATIONS_UA" "elif"
# Final else case
compile_and_append "LNG_EN" "TRANSLATIONS_EN" "else"
# End of cfn_sort.cpp
cat <<EOF >> radio/src/cfn_sort.cpp
cat <<EOF >> "${OUTPUT_FILE}"
#endif
};
@ -116,4 +129,5 @@ uint8_t getFuncSortIdx(uint8_t func)
}
EOF
[ -f ./a.out ] && rm ./a.out
# Clean up
[ -f "${EXECUTABLE}" ] && rm "${EXECUTABLE}"

351
tools/check_translations.py Executable file
View file

@ -0,0 +1,351 @@
#!/usr/bin/env python3
"""
Translation Checker for EdgeTX Translations
This script can check:
1. Bootloader translations (bl_translations.h) - unified file with multiple languages
2. Individual language translation files (*.h) - separate files per language
It ensures that all translation languages have the same set of translation strings defined.
"""
import re
import sys
import os
from pathlib import Path
from collections import defaultdict
from typing import Dict, Set, List, Tuple, Optional
import argparse
import glob
class TranslationChecker:
def __init__(self):
self.bootloader_translations = defaultdict(set) # language -> set of bootloader translation keys
self.language_translations = defaultdict(set) # language -> set of language translation keys
self.bootloader_keys = set()
self.language_keys = set()
self.checked_files = []
def find_translations_directory(self, start_path: str) -> Optional[Path]:
"""Find the translations directory by searching up from the given path."""
current_path = Path(start_path).resolve()
# If the provided path is already the translations directory
if current_path.name == "translations" and current_path.is_dir():
return current_path
# If the provided path is a file in translations directory
if current_path.parent.name == "translations":
return current_path.parent
# Search up the directory tree
while current_path != current_path.parent:
translations_path = current_path / "radio" / "src" / "translations"
if translations_path.exists() and translations_path.is_dir():
return translations_path
current_path = current_path.parent
return None
def parse_bootloader_file(self, file_path: Path):
"""Parse the bootloader translation file (bl_translations.h)."""
if not file_path.exists():
print(f"Warning: File {file_path} does not exist")
return False
with open(file_path, 'r', encoding='utf-8') as f:
content = f.read()
# Split content into lines for easier processing
lines = content.split('\n')
current_language = None
in_translation_block = False
conditional_depth = 0
for i, line in enumerate(lines):
line = line.strip()
# Skip empty lines and comments
if not line or line.startswith('//'):
continue
# Check for translation language blocks
translation_match = re.match(r'#(?:if|elif)\s+defined\(TRANSLATIONS_([A-Z]+)\)', line)
if translation_match:
current_language = translation_match.group(1)
in_translation_block = True
conditional_depth = 0
continue
# Check for else block (default/English)
if re.match(r'#else', line) and in_translation_block and conditional_depth == 0:
current_language = "EN" # Default language
continue
# Track conditional compilation depth
if re.match(r'#if', line) and in_translation_block:
conditional_depth += 1
continue
elif re.match(r'#endif', line) and in_translation_block:
if conditional_depth > 0:
conditional_depth -= 1
else:
# This is the end of the translation block
in_translation_block = False
current_language = None
continue
# Skip non-translation lines
if not in_translation_block or not current_language:
continue
# Parse #define statements
define_match = re.match(r'#define\s+(TR_BL_\w+)', line)
if define_match:
key = define_match.group(1)
self.bootloader_translations[current_language].add(key)
self.bootloader_keys.add(key)
self.checked_files.append(str(file_path))
return True
def parse_language_file(self, file_path: Path) -> Optional[str]:
"""Parse an individual language translation file (e.g., en.h, fr.h)."""
if not file_path.exists():
return None
# Extract language code from filename
language = file_path.stem.upper()
with open(file_path, 'r', encoding='utf-8', errors='ignore') as f:
content = f.read()
# Find all #define TR_ statements
define_matches = re.findall(r'#define\s+(TR_\w+)', content)
for key in define_matches:
self.language_translations[language].add(key)
self.language_keys.add(key)
self.checked_files.append(str(file_path))
return language
def check_bootloader_translations(self, translations_dir: Path) -> bool:
"""Check bootloader translations."""
bl_file = translations_dir / "bl_translations.h"
return self.parse_bootloader_file(bl_file)
def check_language_translations(self, translations_dir: Path) -> List[str]:
"""Check individual language translation files."""
languages_found = []
# Look for .h files that are language files (excluding special files)
exclude_files = {"bl_translations.h", "untranslated.h"}
for h_file in translations_dir.glob("*.h"):
if h_file.name in exclude_files:
continue
# Skip files that are clearly not language files
if h_file.name.startswith("tts_"):
continue
language = self.parse_language_file(h_file)
if language:
languages_found.append(language)
return languages_found
def analyze(self) -> Dict[str, any]:
"""Analyze translations and return results."""
bootloader_languages = list(self.bootloader_translations.keys())
language_languages = list(self.language_translations.keys())
results = {
"bootloader": {
"languages": bootloader_languages,
"total_keys": len(self.bootloader_keys),
"missing_keys": defaultdict(set),
"extra_keys": defaultdict(set),
"summary": {}
},
"language_files": {
"languages": language_languages,
"total_keys": len(self.language_keys),
"missing_keys": defaultdict(set),
"extra_keys": defaultdict(set),
"summary": {}
},
"checked_files": self.checked_files
}
# Analyze bootloader translations
for lang in bootloader_languages:
lang_keys = self.bootloader_translations[lang]
results["bootloader"]["missing_keys"][lang] = self.bootloader_keys - lang_keys
results["bootloader"]["extra_keys"][lang] = lang_keys - self.bootloader_keys
results["bootloader"]["summary"][lang] = {
"total": len(lang_keys),
"missing": len(results["bootloader"]["missing_keys"][lang]),
"extra": len(results["bootloader"]["extra_keys"][lang])
}
# Analyze language file translations
for lang in language_languages:
lang_keys = self.language_translations[lang]
results["language_files"]["missing_keys"][lang] = self.language_keys - lang_keys
results["language_files"]["extra_keys"][lang] = lang_keys - self.language_keys
results["language_files"]["summary"][lang] = {
"total": len(lang_keys),
"missing": len(results["language_files"]["missing_keys"][lang]),
"extra": len(results["language_files"]["extra_keys"][lang])
}
return results
def print_report(self, verbose=False):
"""Print a detailed report of the translation analysis."""
results = self.analyze()
has_bootloader = bool(results["bootloader"]["languages"])
has_language_files = bool(results["language_files"]["languages"])
if not has_bootloader and not has_language_files:
print("No translation files found or processed.")
return
print("EdgeTX Translation Analysis")
print("=" * 50)
if verbose:
print(f"Checked files: {len(results['checked_files'])}")
for file_path in results['checked_files']:
print(f" - {file_path}")
print()
# Report bootloader translations
if has_bootloader:
self._print_section_report("Bootloader Translations (bl_translations.h)",
results["bootloader"], self.bootloader_keys, verbose)
# Report language file translations
if has_language_files:
self._print_section_report("Language File Translations (*.h)",
results["language_files"], self.language_keys, verbose)
def _print_section_report(self, title: str, section_results: Dict, all_keys: Set, verbose: bool):
"""Print report for a specific section (bootloader or language files)."""
print(f"\n{title}")
print("-" * len(title))
print(f"Total unique translation keys: {section_results['total_keys']}")
print(f"Languages found: {', '.join(sorted(section_results['languages']))}")
print()
# Summary table
print("Summary by Language:")
print("-" * 60)
print(f"{'Language':<12} {'Total':<8} {'Missing':<10} {'Extra':<8}")
print("-" * 60)
for lang in sorted(section_results['languages']):
summary = section_results['summary'][lang]
print(f"{lang:<12} {summary['total']:<8} {summary['missing']:<10} {summary['extra']:<8}")
print()
# Detailed missing keys report
has_issues = False
for lang in sorted(section_results['languages']):
missing = section_results['missing_keys'][lang]
extra = section_results['extra_keys'][lang]
if missing or extra:
has_issues = True
print(f"Issues for {lang}:")
if missing:
print(f" Missing keys ({len(missing)}):")
for key in sorted(missing):
print(f" - {key}")
if extra:
print(f" Extra keys ({len(extra)}):")
for key in sorted(extra):
print(f" + {key}")
print()
if not has_issues:
print("✅ All languages have consistent translation keys!")
else:
print("❌ Translation inconsistencies found!")
# Show all translation keys for reference (only if verbose)
if verbose:
print(f"\nAll Translation Keys ({len(all_keys)}):")
print("-" * 40)
for key in sorted(all_keys):
print(f" {key}")
print()
def main():
parser = argparse.ArgumentParser(
description="Check EdgeTX translation consistency",
epilog="""
Examples:
# Check bootloader translations only
python3 check_translations.py --bootloader
# Check individual language files only
python3 check_translations.py --languages
# Check both (default)
python3 check_translations.py
# Check with verbose output
python3 check_translations.py -v
# Specify path to translations directory or EdgeTX root
python3 check_translations.py /path/to/edgetx/radio/src/translations
""",
formatter_class=argparse.RawDescriptionHelpFormatter
)
parser.add_argument("path", nargs="?", default=".",
help="Path to translations directory, EdgeTX root, or current directory")
parser.add_argument("-v", "--verbose", action="store_true",
help="Show all translation keys")
parser.add_argument("--bootloader", action="store_true",
help="Check only bootloader translations (bl_translations.h)")
parser.add_argument("--languages", action="store_true",
help="Check only individual language files (*.h)")
args = parser.parse_args()
checker = TranslationChecker()
# Find translations directory
translations_dir = checker.find_translations_directory(args.path)
if not translations_dir:
print(f"Error: Could not find translations directory from path: {args.path}")
print("Please specify a path to the EdgeTX repository root or translations directory.")
sys.exit(1)
print(f"Using translations directory: {translations_dir}")
print()
# Determine what to check
check_bootloader = args.bootloader or not args.languages
check_languages = args.languages or not args.bootloader
if check_bootloader:
if not checker.check_bootloader_translations(translations_dir):
print("Warning: Could not process bootloader translations")
if check_languages:
languages_found = checker.check_language_translations(translations_dir)
if not languages_found:
print("Warning: No individual language translation files found")
checker.print_report(verbose=args.verbose)
if __name__ == "__main__":
main()

View file

@ -0,0 +1,82 @@
#!/bin/bash
# Stops on first error, echo on
set -e
set -x
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
. "$SCRIPT_DIR/build-common.sh"
# Determine number of CPU cores for parallel builds
if [ -f /usr/bin/nproc ]; then
num_cpus=$(nproc)
elif [ "$(uname)" = "Darwin" ]; then
num_cpus=$(sysctl -n hw.ncpu)
elif [ -f /usr/sbin/sysctl ]; then
num_cpus=$(sysctl -n hw.logicalcpu)
else
num_cpus=2
fi
: "${JOBS:=$num_cpus}"
# Parse command line arguments
while [ $# -gt 0 ]
do
case "$1" in
--jobs=*)
JOBS="${1#*=}";;
-j*)
JOBS="${1#*j}";;
-h|--help)
echo "Usage: $0 [-j<jobs>|--jobs=<jobs>]"
echo "Build companion translations using tx16s target configuration"
echo ""
echo "Options:"
echo " -j<jobs>, --jobs=<jobs> Number of parallel jobs (default: $num_cpus)"
echo " -h, --help Show this help message"
exit 0;;
-*)
echo >&2 "usage: $0 [-j<jobs>|--jobs=<jobs>]"
echo >&2 "Use -h or --help for more information"
exit 1;;
*)
echo >&2 "usage: $0 [-j<jobs>|--jobs=<jobs>]"
echo >&2 "Use -h or --help for more information"
exit 1;;
esac
shift
done
# Project root directory (one level up from tools)
PROJECT_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)"
# Set common build options
: "${BUILD_TYPE:=Release}"
: "${COMMON_OPTIONS:="-DCMAKE_BUILD_TYPE=$BUILD_TYPE -DCMAKE_RULE_MESSAGES=OFF -Wno-dev"}"
BUILD_OPTIONS="${COMMON_OPTIONS} "
echo "Building companion translations with tx16s target configuration..."
# Get tx16s target build options from build-common.sh
if ! get_target_build_options "tx16s"; then
echo "Error: Failed to find a match for target 'tx16s'"
exit 1
fi
# Ensure we're in the project root
cd "${PROJECT_ROOT}"
# Create and enter build directory
rm -rf build
mkdir build
cd build
echo "Configuring build with options: ${BUILD_OPTIONS}"
cmake ${BUILD_OPTIONS} "${PROJECT_ROOT}"
echo "Configuring native build..."
cmake --build . --target native-configure
echo "Building companion translations with ${JOBS} parallel jobs..."
make -j"${JOBS}" -C native companion_translations