#!/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 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 "$@"