website-htmx-rustelo-code/scripts/others/validate-fluent.sh

361 lines
9.2 KiB
Bash
Raw Permalink Normal View History

2026-07-10 03:44:13 +01:00
#!/usr/bin/env bash
# Fluent Validation Script using native工具和Rust
# Validates Fluent (.ftl) files for syntax errors and structural issues
set -euo pipefail
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
CYAN='\033[0;36m'
NC='\033[0m' # No Color
# Configuration
CONTENT_DIR="content"
VERBOSE=${VERBOSE:-false}
STRICT=${STRICT:-false}
REPORT_FILE=""
# Error tracking
ERRORS=0
WARNINGS=0
TOTAL_FILES=0
print_usage() {
cat << EOF
Usage: $0 [OPTIONS] [DIRECTORY]
Validate Fluent (.ftl) localization files for syntax and structure errors.
OPTIONS:
-f, --file FILE Validate specific FTL file
-v, --verbose Enable verbose output
-s, --strict Treat warnings as errors
-r, --report FILE Generate JSON report file
-h, --help Show this help message
EXAMPLES:
$0 # Validate all FTL files in content/
$0 content/en # Validate English FTL files
$0 -f content/en/auth.ftl # Validate specific file
$0 -v -r report.json # Verbose with JSON report
ENVIRONMENT VARIABLES:
VERBOSE=true Enable verbose output
STRICT=true Treat warnings as errors
EOF
}
log() {
echo -e "${CYAN}[VALIDATE]${NC} $1"
}
log_error() {
echo -e "${RED}[ERROR]${NC} $1" >&2
((ERRORS++))
}
log_warning() {
echo -e "${YELLOW}[WARN]${NC} $1"
((WARNINGS++))
}
log_success() {
echo -e "${GREEN}[OK]${NC} $1"
}
# Check if required tools are available
check_dependencies() {
if ! command -v cargo &> /dev/null; then
log_error "Cargo not found. Please install Rust toolchain."
exit 1
fi
}
# Install fluent-syntax tool if needed
install_fluent_tool() {
if ! command -v fluent-syntax &> /dev/null; then
log "Creating simple fluent parser..."
# Create a simple validate tool if fluent-cli not available
cat > /tmp/validate_ftl.rs << 'EOF'
use std::fs;
use std::env;
use std::process;
fn validate_ftl_file(file_path: &str) -> Result<(), String> {
let content = fs::read_to_string(file_path)
.map_err(|e| format!("Failed to read file: {}", e))?;
// Basic FTL validation: check for unbalanced braces
let open_braces = content.chars().filter(|&c| c == '{').count();
let close_braces = content.chars().filter(|&c| c == '}').count();
if open_braces != close_braces {
return Err(format!("Unbalanced braces: {} open, {} close", open_braces, close_braces));
}
// Basic syntax validation
for (line_num, line) in content.lines().enumerate() {
let line = line.trim();
if line.is_empty() || line.starts_with('#') {
continue;
}
// Check for message format
if line.contains('=') && !line.starts_with('[') {
let parts: Vec<&str> = line.splitn(2, '=').collect();
if parts.len() != 2 {
return Err(format!("Line {}: Invalid message format", line_num + 1));
}
let key = parts[0].trim();
if key.is_empty() {
return Err(format!("Line {}: Empty message key", line_num + 1));
}
// Check for valid key characters
if !key.chars().all(|c| c.is_alphanumeric() || c == '-' || c == '_') {
return Err(format!("Line {}: Invalid key characters: {}", line_num + 1, key));
}
}
}
Ok(())
}
fn main() {
let args: Vec<String> = env::args().collect();
if args.len() < 2 {
eprintln!("Usage: validate_ftl <file.ftl>");
process::exit(1);
}
match validate_ftl_file(&args[1]) {
Ok(()) => {
println!("✓ {}: Valid", args[1]);
process::exit(0);
}
Err(e) => {
eprintln!("✗ {}: {}", args[1], e);
process::exit(1);
}
}
}
EOF
rustc /tmp/validate_ftl.rs -o /tmp/validate_ftl
export PATH="/tmp:$PATH"
fi
}
# Validate a single FTL file
validate_ftl_file() {
local file="$1"
local errors=0
if [[ ! -f "$file" ]]; then
log_error "File not found: $file"
return 1
fi
((TOTAL_FILES++))
log "Validating: $file"
# Check UTF-8 encoding
if ! iconv -f UTF-8 -t UTF-8 "$file" >/dev/null 2>&1; then
log_error "File is not valid UTF-8: $file"
((errors++))
fi
# Check for BOM (Byte Order Mark)
if head -c 3 "$file" | grep -q '\xEF\xBB\xBF'; then
log_warning "File contains UTF-8 BOM: $file"
fi
# Check for mixed line endings
if file "$file" | grep -q "CRLF"; then
log_warning "Mixed line endings (CRLF detected): $file"
fi
# Use basic validation tool
if command -v fluent-syntax &> /dev/null; then
if fluent-syntax validate "$file" > /dev/null 2>&1; then
log_success "$file: Valid syntax"
else
log_error "$file: Syntax error"
if [[ "$VERBOSE" == "true" ]]; then
fluent-syntax validate "$file"
fi
((errors++))
fi
else
# Use simple validation
if /tmp/validate_ftl "$file" 2>/dev/null; then
log_success "$file: Valid"
else
log_error "$file: Validation failed"
((errors++))
fi
fi
# Custom checks for common issues
local content
content=$(cat "$file")
# Check for unbalanced braces
local open_braces
local close_braces
open_braces=$(grep -o '{' <<< "$content" | wc -l)
close_braces=$(grep -o '}' <<< "$content" | wc -l)
if [[ $open_braces -ne $close_braces ]]; then
log_error "$file: Unbalanced braces ($open_braces open, $close_braces close)"
((errors++))
fi
# Check for empty messages
while IFS= read -r line; do
if [[ "$line" =~ ^[a-zA-Z0-9_-]+[[:space:]]*=[[:space:]]*$ ]]; then
log_warning "$file: Empty message value: $line"
fi
done <<< "$content"
# Check for duplicate keys
local keys
keys=$(grep -E '^[a-zA-Z0-9\-_]+[[:space:]]*=' "$file" | cut -d'=' -f1 | sed 's/[[:space:]]*$//')
local duplicate_keys
duplicate_keys=$(echo "$keys" | sort | uniq -d)
if [[ -n "$duplicate_keys" ]]; then
log_error "$file: Duplicate keys found: $duplicate_keys"
((errors++))
fi
return $errors
}
# Validate all FTL files in directory
validate_directory() {
local dir="$1"
local files_found=0
if [[ ! -d "$dir" ]]; then
log_error "Directory not found: $dir"
return 1
fi
log "Searching for FTL files in: $dir"
# Find all .ftl files recursively
while IFS= read -r -d '' file; do
validate_ftl_file "$file"
((files_found++))
done < <(find "$dir" -name "*.ftl" -type f -print0)
if [[ $files_found -eq 0 ]]; then
log_warning "No FTL files found in: $dir"
fi
}
# Generate JSON report
generate_report() {
local output_file="$1"
cat > "$output_file" << EOF
{
"validation": {
"total_files": $TOTAL_FILES,
"errors": $ERRORS,
"warnings": $WARNINGS,
"success": $(($TOTAL_FILES - $ERRORS)),
"timestamp": "$(date -Iseconds)",
"content_dir": "$CONTENT_DIR"
}
}
EOF
log "Report saved to: $output_file"
}
# Main validation function
main() {
local target_dir="$CONTENT_DIR"
local target_file=""
# Parse command line arguments
while [[ $# -gt 0 ]]; do
case $1 in
-f|--file)
target_file="$2"
shift 2
;;
-v|--verbose)
VERBOSE=true
shift
;;
-s|--strict)
STRICT=true
shift
;;
-r|--report)
REPORT_FILE="$2"
shift 2
;;
-h|--help)
print_usage
exit 0
;;
*)
target_dir="$1"
shift
;;
esac
done
# Override with environment variables
[[ -n "${VERBOSE:-}" ]] && VERBOSE=true
[[ -n "${STRICT:-}" ]] && STRICT=true
log "Starting Fluent validation"
check_dependencies
install_fluent_tool
if [[ -n "$target_file" ]]; then
validate_ftl_file "$target_file"
else
validate_directory "$target_dir"
fi
# Generate report if requested
if [[ -n "$REPORT_FILE" ]]; then
generate_report "$REPORT_FILE"
fi
# Final summary
echo
echo "=== VALIDATION SUMMARY ==="
echo "Total files processed: $TOTAL_FILES"
echo -e "Errors: ${RED}$ERRORS${NC}"
echo -e "Warnings: ${YELLOW}$WARNINGS${NC}"
if [[ $ERRORS -gt 0 ]]; then
echo -e "${RED}✗ Validation failed with errors${NC}"
exit 1
elif [[ $WARNINGS -gt 0 ]] && [[ "$STRICT" == "true" ]]; then
echo -e "${YELLOW}⚠ Validation failed with warnings (strict mode)${NC}"
exit 1
elif [[ $WARNINGS -gt 0 ]]; then
echo -e "${YELLOW}⚠ Validation passed with warnings${NC}"
exit 0
else
echo -e "${GREEN}✓ Validation passed successfully${NC}"
exit 0
fi
}
# Run main function if script is executed directly
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
main "$@"
fi