wp-plugin-swiss-football-ma.../dev-scripts/i18n-manage.sh
2026-03-27 13:59:28 +01:00

523 lines
17 KiB
Bash
Executable File
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/bin/bash
################################################################################
# Swiss Football Matchdata - i18n Management Script
#
# Complete i18n workflow using wp-cli:
# 1. Extract strings from PHP/JS to .pot file
# 2. Translate .pot to specified languages
# 3. Generate .mo files from .po files
# 4. Clean up regional language variants
#
# Usage:
# ./dev-scripts/i18n-manage.sh [extract|translate|build|clean|all]
#
# Commands:
# extract - Extract i18n strings and generate .pot file
# translate - Generate .po files for all languages (requires jq)
# build - Generate .mo files from .po files
# clean - Remove regional language variants
# all - Run all steps: extract → translate → build → clean
#
# Configuration:
# Modify the CONFIG section below to match your environment
#
# Dependencies:
# - wp-cli (via docker compose when WP_CLI_WRAPPER is used)
# - jq (for JSON translation data handling)
# - msgfmt (for generating .mo files)
#
################################################################################
set -e
################################################################################
# CONFIGURATION - Modify these values for your environment
################################################################################
# WordPress CLI command wrapper (for docker environments)
# Use local wp-cli: WP_CLI_CMD="wp"
# Use docker compose with working directory: WP_CLI_CMD="docker compose -f ~/Development/wordpress-dev/wp-dev-compose.yml run -w /var/www/html/wp-content/plugins/swiss-football-matchdata --rm wp-cli"
WP_CLI_CMD="docker compose -f ~/Development/wordpress-dev/wp-dev-compose.yml run -w /var/www/html/wp-content/plugins/swiss-football-matchdata --rm wp-cli"
# Plugin slug
PLUGIN_SLUG="swi_foot_matchdata"
# Text domain (must match in plugin)
TEXT_DOMAIN="swi_foot_matchdata"
# Languages to support: language_code:language_name
# Format: LANGUAGE_CODE|LANGUAGE_NAME (space-separated for compatibility)
LANGUAGES="de_DE:German fr_FR:French it_IT:Italian en_US:English"
# Regional variants to clean up (remove if they exist)
# These will be removed to keep only the main language codes
REGIONAL_VARIANTS=(
"de_AT" # Austrian German
"de_CH" # Swiss German
)
################################################################################
# COLORS & OUTPUT
################################################################################
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
CYAN='\033[0;36m'
NC='\033[0m'
log_info() {
echo -e "${BLUE}${NC} $1"
}
log_success() {
echo -e "${GREEN}${NC} $1"
}
log_warning() {
echo -e "${YELLOW}${NC} $1"
}
log_error() {
echo -e "${RED}${NC} $1"
}
log_section() {
echo -e "\n${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
echo -e "${CYAN} $1${NC}"
echo -e "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}\n"
}
################################################################################
# UTILITY FUNCTIONS
################################################################################
check_dependencies() {
local missing_deps=0
# Check for wp-cli
if ! eval "$WP_CLI_CMD --version" &>/dev/null; then
log_error "wp-cli not found or not accessible via: $WP_CLI_CMD"
missing_deps=1
fi
# Check for msgfmt (gettext tools)
if ! command -v msgfmt &>/dev/null; then
log_error "msgfmt not found. Install gettext tools:"
echo " macOS: brew install gettext"
echo " Ubuntu: sudo apt-get install gettext"
missing_deps=1
fi
# Check for jq (optional but recommended)
if ! command -v jq &>/dev/null; then
log_warning "jq not installed (optional). Install for better translation support:"
echo " macOS: brew install jq"
echo " Ubuntu: sudo apt-get install jq"
fi
if [[ $missing_deps -eq 1 ]]; then
return 1
fi
return 0
}
get_language_name() {
local code=$1
for pair in $LANGUAGES; do
local lang_code="${pair%:*}"
local lang_name="${pair#*:}"
if [[ "$lang_code" == "$code" ]]; then
echo "$lang_name"
return 0
fi
done
echo "Unknown"
}
################################################################################
# EXTRACTION
################################################################################
extract_strings() {
log_section "Extracting i18n Strings"
local pot_file="languages/${PLUGIN_SLUG}.pot"
local exclude_patterns="node_modules,vendor,assets/build,wp-json"
log_info "Extracting strings to: $pot_file"
# Create languages directory if it doesn't exist
mkdir -p languages
# Use wp-cli to extract strings
# Note: wp-cli automatically handles:
# - Translators comments (/* translators: ... */)
# - Multiple placeholders with ordered syntax (%1$d, %2$s, etc.)
# - WordPress i18n functions (__, _e, _x, _n, etc.)
# Syntax: wp i18n make-pot <source-directory> <output-file-path>
log_info "Running wp-cli extraction command..."
log_info " Working directory (Docker): /var/www/html/wp-content/plugins/swiss-football-matchdata"
log_info " Plugin directory (local): $(pwd)"
# Execute the extraction with proper output handling
# WP_CLI_CMD is already configured with -w flag, so . refers to the plugin directory
local extract_output
extract_output=$(eval "$WP_CLI_CMD i18n make-pot --exclude=$exclude_patterns --domain=$TEXT_DOMAIN . languages/${PLUGIN_SLUG}.pot 2>&1" || echo "FAILED")
# Small delay to ensure file is written
sleep 1
# Check if extraction was successful by verifying the file exists
local retries=3
local pot_exists=0
while [[ $retries -gt 0 ]] && [[ $pot_exists -eq 0 ]]; do
if [[ -f "$pot_file" ]]; then
pot_exists=1
break
fi
((retries--))
if [[ $retries -gt 0 ]]; then
log_warning "Pot file not found yet, retrying... ($retries attempts left)"
sleep 1
fi
done
if [[ $pot_exists -eq 1 ]]; then
log_success "Extracted strings to: $pot_file"
# Show statistics
local string_count=$(grep -c "^msgid \"" "$pot_file" 2>/dev/null || echo "0")
string_count=$(echo "$string_count" | tr -d ' \n')
log_info "Total strings found: $string_count"
# Check for obsolete entries
local obsolete_count=$(grep -c "^#~" "$pot_file" 2>/dev/null || echo "0")
obsolete_count=$(echo "$obsolete_count" | tr -d ' \n')
if [[ $obsolete_count -gt 0 ]]; then
log_warning "Found $obsolete_count obsolete entries (marked with #~)"
fi
return 0
else
log_error "Failed to generate .pot file"
log_error "wp-cli output: $extract_output"
log_error "Checked path: $pot_file (local filesystem)"
log_info "Directory context:"
log_info " • Local plugin dir: $(pwd)"
log_info " • Docker plugin dir: /var/www/html/wp-content/plugins/swiss-football-matchdata"
log_info " • Expected output (local): $(pwd)/languages/${PLUGIN_SLUG}.pot"
log_info ""
log_info "If the file exists in Docker but not locally, verify:"
log_info " 1. Docker volume mounting is correct"
log_info " 2. Run: docker compose -f ~/Development/wordpress-dev/wp-dev-compose.yml exec wp-cli ls -la /var/www/html/wp-content/plugins/swiss-football-matchdata/"
return 1
fi
}
################################################################################
# TRANSLATION
################################################################################
translate_strings() {
log_section "Generating .po files from .pot"
local pot_file="languages/${PLUGIN_SLUG}.pot"
if [[ ! -f "$pot_file" ]]; then
log_error ".pot file not found: $pot_file"
log_info "Run 'extract' command first"
return 1
fi
local created_count=0
for pair in $LANGUAGES; do
local lang_code="${pair%:*}"
local lang_name="${pair#*:}"
local po_file="languages/${PLUGIN_SLUG}-${lang_code}.po"
log_info "Processing: $lang_name ($lang_code)"
if [[ -f "$po_file" ]]; then
# Update existing .po file
log_info " Updating existing .po file..."
eval "$WP_CLI_CMD i18n update-po languages/${PLUGIN_SLUG}.pot languages/${PLUGIN_SLUG}-${lang_code}.po 2>/dev/null" || true
log_success " Updated: $po_file"
else
# Create new .po file from .pot
log_info " Creating new .po file..."
cp "$pot_file" "$po_file"
# Update language headers in .po file
sed -i.bak "s/Language: /Language: ${lang_code}/" "$po_file"
rm -f "${po_file}.bak"
log_success " Created: $po_file"
((created_count++))
fi
done
log_success "Created/Updated $created_count translation files"
return 0
}
################################################################################
# BUILD .MO FILES
################################################################################
build_mo_files() {
log_section "Generating .mo files"
local compiled_count=0
local errors=0
for pair in $LANGUAGES; do
local lang_code="${pair%:*}"
local lang_name="${pair#*:}"
local po_file="languages/${PLUGIN_SLUG}-${lang_code}.po"
local mo_file="languages/${PLUGIN_SLUG}-${lang_code}.mo"
if [[ -f "$po_file" ]]; then
log_info "Compiling: $lang_name ($lang_code)"
if msgfmt -o "$mo_file" "$po_file" 2>/dev/null; then
log_success " Generated: $mo_file"
((compiled_count++))
else
log_error " Failed to compile: $po_file"
((errors++))
fi
else
log_warning " .po file not found: $po_file"
fi
done
log_success "Compiled $compiled_count .mo files"
if [[ $errors -gt 0 ]]; then
log_warning "Encountered $errors errors during compilation"
return 1
fi
return 0
}
################################################################################
# CLEANUP
################################################################################
cleanup_regional_variants() {
log_section "Cleaning up regional language variants"
local removed_count=0
for variant in "${REGIONAL_VARIANTS[@]}"; do
# Check for .po files
local po_file="languages/${PLUGIN_SLUG}-${variant}.po"
if [[ -f "$po_file" ]]; then
log_info "Removing: $po_file"
rm -f "$po_file"
((removed_count++))
fi
# Check for .mo files
local mo_file="languages/${PLUGIN_SLUG}-${variant}.mo"
if [[ -f "$mo_file" ]]; then
log_info "Removing: $mo_file"
rm -f "$mo_file"
((removed_count++))
fi
# Also check for backup files created by sed
local po_backup="${po_file}.bak"
if [[ -f "$po_backup" ]]; then
rm -f "$po_backup"
fi
done
if [[ $removed_count -eq 0 ]]; then
log_info "No regional variants found to remove"
else
log_success "Removed $removed_count regional variant files"
fi
return 0
}
################################################################################
# DISPLAY SUMMARY
################################################################################
show_summary() {
log_section "i18n Management Summary"
echo -e "${YELLOW}📁 Language Files:${NC}\n"
for pair in $LANGUAGES; do
local lang_code="${pair%:*}"
local lang_name="${pair#*:}"
local po_file="languages/${PLUGIN_SLUG}-${lang_code}.po"
local mo_file="languages/${PLUGIN_SLUG}-${lang_code}.mo"
printf " %-20s %-20s " "$lang_code" "$lang_name"
if [[ -f "$po_file" ]] && [[ -f "$mo_file" ]]; then
local po_size=$(du -h "$po_file" | cut -f1)
local mo_size=$(du -h "$mo_file" | cut -f1)
echo -e "${GREEN}${NC} PO: $po_size, MO: $mo_size"
elif [[ -f "$po_file" ]]; then
local po_size=$(du -h "$po_file" | cut -f1)
echo -e "${YELLOW}${NC} PO: $po_size (no .mo)"
else
echo -e "${RED}${NC} Missing files"
fi
done
local lang_count=$(echo "$LANGUAGES" | wc -w)
echo -e "\n${YELLOW}Configuration:${NC}\n"
echo " Plugin slug: $PLUGIN_SLUG"
echo " Text domain: $TEXT_DOMAIN"
echo " Languages: $lang_count"
echo " Languages dir: languages/"
echo -e "\n${YELLOW}Next Steps:${NC}\n"
echo " • Verify .po files contain all necessary translations"
echo " • Update empty translation entries in .po files"
echo " • Re-run 'build' to regenerate .mo files after translations"
echo ""
}
################################################################################
# HELP
################################################################################
show_help() {
cat << 'EOF'
Usage: ./dev-scripts/i18n-manage.sh [COMMAND]
COMMANDS:
extract Extract i18n strings from PHP/JS files to .pot
translate Generate .po files for all configured languages
build Compile .po files to .mo files
clean Remove regional language variants
all Run all steps: extract → translate → build → clean
help Show this help message
EXAMPLES:
# Extract strings and generate translations
./dev-scripts/i18n-manage.sh extract
# Generate .po files for translation
./dev-scripts/i18n-manage.sh translate
# Compile .po to .mo files
./dev-scripts/i18n-manage.sh build
# Run complete workflow
./dev-scripts/i18n-manage.sh all
# Clean up regional variants
./dev-scripts/i18n-manage.sh clean
CONFIGURATION:
Edit the script's CONFIG section to adjust:
- WP_CLI_CMD: WordPress CLI command wrapper
- LANGUAGES: Supported language codes
- REGIONAL_VARIANTS: Language variants to remove
TRANSLATORS COMMENTS:
In PHP code, add translators comments above i18n functions:
/* translators: %s is the error message from the API */
__('Error loading teams: %s', 'swi_foot_matchdata')
For multiple placeholders, use ordered syntax:
/* translators: %1$d is count, %2$d is duration in seconds */
__('Currently caching %1$d records with %2$d second duration.', 'swi_foot_matchdata')
SUPPORTED LANGUAGES (default):
de_DE - German
fr_FR - French
it_IT - Italian
en_US - English (US)
DEPENDENCIES:
- wp-cli (WordPress CLI)
- msgfmt (gettext tools)
- jq (optional, for translation management)
EOF
}
################################################################################
# MAIN
################################################################################
main() {
local command="${1:-all}"
# Show banner
echo -e "${CYAN}╔════════════════════════════════════════════════╗${NC}"
echo -e "${CYAN}║ Swiss Football Matchdata - i18n Management ║${NC}"
echo -e "${CYAN}╚════════════════════════════════════════════════╝${NC}\n"
# Check dependencies
log_info "Checking dependencies..."
if ! check_dependencies; then
log_error "Missing required dependencies. Please install and try again."
exit 1
fi
log_success "All dependencies available\n"
# Execute command
case "$command" in
extract)
extract_strings
;;
translate)
translate_strings
show_summary
;;
build)
build_mo_files
show_summary
;;
clean)
cleanup_regional_variants
;;
all)
extract_strings && \
translate_strings && \
build_mo_files && \
cleanup_regional_variants && \
show_summary
;;
help|--help|-h)
show_help
;;
*)
log_error "Unknown command: $command"
echo ""
show_help
exit 1
;;
esac
local exit_code=$?
if [[ $exit_code -eq 0 ]]; then
log_success "\nTask completed successfully!"
else
log_error "\nTask failed with exit code $exit_code"
fi
exit $exit_code
}
# Run main function with all arguments
main "$@"