686 lines
24 KiB
Bash
686 lines
24 KiB
Bash
|
|
#!/bin/bash
|
|||
|
|
|
|||
|
|
# Content synchronization script for localized blog and recipe content
|
|||
|
|
# Synchronizes content structure and metadata across languages while preserving translations
|
|||
|
|
|
|||
|
|
set -e
|
|||
|
|
|
|||
|
|
# Configuration
|
|||
|
|
CONTENT_DIR="content"
|
|||
|
|
SUPPORTED_LANGUAGES=("en" "es")
|
|||
|
|
DEFAULT_LANGUAGE="en"
|
|||
|
|
BACKUP_DIR="content_backup_$(date +%Y%m%d_%H%M%S)"
|
|||
|
|
|
|||
|
|
# Colors for output
|
|||
|
|
RED='\033[0;31m'
|
|||
|
|
GREEN='\033[0;32m'
|
|||
|
|
YELLOW='\033[1;33m'
|
|||
|
|
BLUE='\033[0;34m'
|
|||
|
|
CYAN='\033[0;36m'
|
|||
|
|
PURPLE='\033[0;35m'
|
|||
|
|
NC='\033[0m' # No Color
|
|||
|
|
|
|||
|
|
# Helper functions
|
|||
|
|
log_error() {
|
|||
|
|
echo -e "${RED}❌ ERROR: $1${NC}"
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
log_warning() {
|
|||
|
|
echo -e "${YELLOW}⚠️ WARNING: $1${NC}"
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
log_success() {
|
|||
|
|
echo -e "${GREEN}✅ $1${NC}"
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
log_info() {
|
|||
|
|
echo -e "${BLUE}ℹ️ $1${NC}"
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
log_step() {
|
|||
|
|
echo -e "${CYAN}🔧 $1${NC}"
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
log_sync() {
|
|||
|
|
echo -e "${PURPLE}🔄 $1${NC}"
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
# Display usage information
|
|||
|
|
show_usage() {
|
|||
|
|
echo -e "${BLUE}🔄 Content Synchronization Tool${NC}"
|
|||
|
|
echo "=================================="
|
|||
|
|
echo ""
|
|||
|
|
echo "Usage: $0 [COMMAND] [OPTIONS]"
|
|||
|
|
echo ""
|
|||
|
|
echo "Commands:"
|
|||
|
|
echo " sync-structure Synchronize content structure across languages"
|
|||
|
|
echo " sync-metadata Synchronize metadata (keeping translations intact)"
|
|||
|
|
echo " check-missing Check for missing translations"
|
|||
|
|
echo " generate-missing Generate placeholder content for missing translations"
|
|||
|
|
echo " backup Create backup of current content"
|
|||
|
|
echo " restore Restore from backup"
|
|||
|
|
echo " validate Validate content synchronization"
|
|||
|
|
echo " help Show this help message"
|
|||
|
|
echo ""
|
|||
|
|
echo "Options:"
|
|||
|
|
echo " --source-lang Source language for synchronization (default: en)"
|
|||
|
|
echo " --target-lang Target language (default: all supported languages)"
|
|||
|
|
echo " --content-type Content type to sync (blog/recipes/all, default: all)"
|
|||
|
|
echo " --dry-run Show what would be done without making changes"
|
|||
|
|
echo " --force Force overwrite existing files"
|
|||
|
|
echo " --backup Create backup before changes"
|
|||
|
|
echo ""
|
|||
|
|
echo "Examples:"
|
|||
|
|
echo " $0 sync-structure --source-lang en --target-lang es"
|
|||
|
|
echo " $0 check-missing --content-type blog"
|
|||
|
|
echo " $0 generate-missing --dry-run"
|
|||
|
|
echo " $0 backup"
|
|||
|
|
echo ""
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
# Parse command line arguments
|
|||
|
|
parse_args() {
|
|||
|
|
SOURCE_LANG="$DEFAULT_LANGUAGE"
|
|||
|
|
TARGET_LANG="all"
|
|||
|
|
CONTENT_TYPE="all"
|
|||
|
|
DRY_RUN=false
|
|||
|
|
FORCE=false
|
|||
|
|
CREATE_BACKUP=false
|
|||
|
|
|
|||
|
|
while [[ $# -gt 0 ]]; do
|
|||
|
|
case $1 in
|
|||
|
|
--source-lang)
|
|||
|
|
SOURCE_LANG="$2"
|
|||
|
|
shift 2
|
|||
|
|
;;
|
|||
|
|
--target-lang)
|
|||
|
|
TARGET_LANG="$2"
|
|||
|
|
shift 2
|
|||
|
|
;;
|
|||
|
|
--content-type)
|
|||
|
|
CONTENT_TYPE="$2"
|
|||
|
|
shift 2
|
|||
|
|
;;
|
|||
|
|
--dry-run)
|
|||
|
|
DRY_RUN=true
|
|||
|
|
shift
|
|||
|
|
;;
|
|||
|
|
--force)
|
|||
|
|
FORCE=true
|
|||
|
|
shift
|
|||
|
|
;;
|
|||
|
|
--backup)
|
|||
|
|
CREATE_BACKUP=true
|
|||
|
|
shift
|
|||
|
|
;;
|
|||
|
|
*)
|
|||
|
|
log_error "Unknown option: $1"
|
|||
|
|
show_usage
|
|||
|
|
exit 1
|
|||
|
|
;;
|
|||
|
|
esac
|
|||
|
|
done
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
# Check dependencies
|
|||
|
|
check_dependencies() {
|
|||
|
|
if ! command -v jq >/dev/null 2>&1; then
|
|||
|
|
log_error "jq is required but not installed. Please install jq to continue."
|
|||
|
|
exit 1
|
|||
|
|
fi
|
|||
|
|
log_success "Dependencies check passed"
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
# Create backup
|
|||
|
|
create_backup() {
|
|||
|
|
log_step "Creating backup of content..."
|
|||
|
|
|
|||
|
|
if [[ -d "$CONTENT_DIR" ]]; then
|
|||
|
|
cp -r "$CONTENT_DIR" "$BACKUP_DIR"
|
|||
|
|
log_success "Backup created: $BACKUP_DIR"
|
|||
|
|
else
|
|||
|
|
log_warning "Content directory not found, no backup created"
|
|||
|
|
fi
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
# Restore from backup
|
|||
|
|
restore_backup() {
|
|||
|
|
local backup_path="$1"
|
|||
|
|
|
|||
|
|
if [[ -z "$backup_path" ]]; then
|
|||
|
|
# Find most recent backup
|
|||
|
|
backup_path=$(ls -1d content_backup_* 2>/dev/null | sort | tail -1)
|
|||
|
|
if [[ -z "$backup_path" ]]; then
|
|||
|
|
log_error "No backup found"
|
|||
|
|
exit 1
|
|||
|
|
fi
|
|||
|
|
fi
|
|||
|
|
|
|||
|
|
if [[ ! -d "$backup_path" ]]; then
|
|||
|
|
log_error "Backup directory not found: $backup_path"
|
|||
|
|
exit 1
|
|||
|
|
fi
|
|||
|
|
|
|||
|
|
log_step "Restoring from backup: $backup_path"
|
|||
|
|
|
|||
|
|
if [[ "$DRY_RUN" == "true" ]]; then
|
|||
|
|
log_info "[DRY RUN] Would restore content from $backup_path"
|
|||
|
|
return
|
|||
|
|
fi
|
|||
|
|
|
|||
|
|
if [[ -d "$CONTENT_DIR" ]]; then
|
|||
|
|
rm -rf "${CONTENT_DIR}.old" 2>/dev/null || true
|
|||
|
|
mv "$CONTENT_DIR" "${CONTENT_DIR}.old"
|
|||
|
|
fi
|
|||
|
|
|
|||
|
|
cp -r "$backup_path" "$CONTENT_DIR"
|
|||
|
|
log_success "Content restored from backup"
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
# Get content indices for a language
|
|||
|
|
get_content_indices() {
|
|||
|
|
local lang="$1"
|
|||
|
|
local content_type="$2"
|
|||
|
|
|
|||
|
|
case "$content_type" in
|
|||
|
|
"blog")
|
|||
|
|
echo "${CONTENT_DIR}/blog/${lang}/index.json"
|
|||
|
|
;;
|
|||
|
|
"recipes")
|
|||
|
|
echo "${CONTENT_DIR}/recipes/${lang}/index.json"
|
|||
|
|
;;
|
|||
|
|
*)
|
|||
|
|
echo "${CONTENT_DIR}/blog/${lang}/index.json ${CONTENT_DIR}/recipes/${lang}/index.json"
|
|||
|
|
;;
|
|||
|
|
esac
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
# Get target languages
|
|||
|
|
get_target_languages() {
|
|||
|
|
if [[ "$TARGET_LANG" == "all" ]]; then
|
|||
|
|
echo "${SUPPORTED_LANGUAGES[@]}"
|
|||
|
|
else
|
|||
|
|
echo "$TARGET_LANG"
|
|||
|
|
fi
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
# Check for missing content
|
|||
|
|
check_missing_content() {
|
|||
|
|
log_step "Checking for missing translations..."
|
|||
|
|
|
|||
|
|
local missing_count=0
|
|||
|
|
local source_indices
|
|||
|
|
|
|||
|
|
if [[ "$CONTENT_TYPE" == "all" ]] || [[ "$CONTENT_TYPE" == "blog" ]]; then
|
|||
|
|
local source_blog="${CONTENT_DIR}/blog/${SOURCE_LANG}/index.json"
|
|||
|
|
|
|||
|
|
if [[ -f "$source_blog" ]]; then
|
|||
|
|
log_info "Checking blog translations..."
|
|||
|
|
|
|||
|
|
local blog_ids=$(jq -r '.blog[].id' "$source_blog" 2>/dev/null || echo "")
|
|||
|
|
|
|||
|
|
for lang in $(get_target_languages); do
|
|||
|
|
if [[ "$lang" == "$SOURCE_LANG" ]]; then
|
|||
|
|
continue
|
|||
|
|
fi
|
|||
|
|
|
|||
|
|
local target_blog="${CONTENT_DIR}/blog/${lang}/index.json"
|
|||
|
|
|
|||
|
|
if [[ ! -f "$target_blog" ]]; then
|
|||
|
|
log_warning "Missing blog index for language: $lang"
|
|||
|
|
((missing_count++))
|
|||
|
|
continue
|
|||
|
|
fi
|
|||
|
|
|
|||
|
|
while IFS= read -r id; do
|
|||
|
|
if [[ -n "$id" && "$id" != "null" ]]; then
|
|||
|
|
local exists=$(jq -r ".blog[] | select(.id == \"$id\") | .id" "$target_blog" 2>/dev/null || echo "")
|
|||
|
|
if [[ -z "$exists" ]]; then
|
|||
|
|
log_warning "Missing blog post translation: '$id' in language '$lang'"
|
|||
|
|
((missing_count++))
|
|||
|
|
fi
|
|||
|
|
fi
|
|||
|
|
done <<< "$blog_ids"
|
|||
|
|
done
|
|||
|
|
fi
|
|||
|
|
fi
|
|||
|
|
|
|||
|
|
if [[ "$CONTENT_TYPE" == "all" ]] || [[ "$CONTENT_TYPE" == "recipes" ]]; then
|
|||
|
|
local source_recipes="${CONTENT_DIR}/recipes/${SOURCE_LANG}/index.json"
|
|||
|
|
|
|||
|
|
if [[ -f "$source_recipes" ]]; then
|
|||
|
|
log_info "Checking recipe translations..."
|
|||
|
|
|
|||
|
|
local recipe_ids=$(jq -r '.recipes[].id' "$source_recipes" 2>/dev/null || echo "")
|
|||
|
|
|
|||
|
|
for lang in $(get_target_languages); do
|
|||
|
|
if [[ "$lang" == "$SOURCE_LANG" ]]; then
|
|||
|
|
continue
|
|||
|
|
fi
|
|||
|
|
|
|||
|
|
local target_recipes="${CONTENT_DIR}/recipes/${lang}/index.json"
|
|||
|
|
|
|||
|
|
if [[ ! -f "$target_recipes" ]]; then
|
|||
|
|
log_warning "Missing recipe index for language: $lang"
|
|||
|
|
((missing_count++))
|
|||
|
|
continue
|
|||
|
|
fi
|
|||
|
|
|
|||
|
|
while IFS= read -r id; do
|
|||
|
|
if [[ -n "$id" && "$id" != "null" ]]; then
|
|||
|
|
local exists=$(jq -r ".recipes[] | select(.id == \"$id\") | .id" "$target_recipes" 2>/dev/null || echo "")
|
|||
|
|
if [[ -z "$exists" ]]; then
|
|||
|
|
log_warning "Missing recipe translation: '$id' in language '$lang'"
|
|||
|
|
((missing_count++))
|
|||
|
|
fi
|
|||
|
|
fi
|
|||
|
|
done <<< "$recipe_ids"
|
|||
|
|
done
|
|||
|
|
fi
|
|||
|
|
fi
|
|||
|
|
|
|||
|
|
if [[ $missing_count -eq 0 ]]; then
|
|||
|
|
log_success "No missing translations found!"
|
|||
|
|
else
|
|||
|
|
log_warning "Found $missing_count missing translations"
|
|||
|
|
fi
|
|||
|
|
|
|||
|
|
return $missing_count
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
# Synchronize content structure
|
|||
|
|
sync_content_structure() {
|
|||
|
|
log_step "Synchronizing content structure from $SOURCE_LANG..."
|
|||
|
|
|
|||
|
|
if [[ "$CREATE_BACKUP" == "true" ]]; then
|
|||
|
|
create_backup
|
|||
|
|
fi
|
|||
|
|
|
|||
|
|
for lang in $(get_target_languages); do
|
|||
|
|
if [[ "$lang" == "$SOURCE_LANG" ]]; then
|
|||
|
|
continue
|
|||
|
|
fi
|
|||
|
|
|
|||
|
|
log_sync "Synchronizing structure for language: $lang"
|
|||
|
|
|
|||
|
|
# Sync blog structure
|
|||
|
|
if [[ "$CONTENT_TYPE" == "all" ]] || [[ "$CONTENT_TYPE" == "blog" ]]; then
|
|||
|
|
local source_blog="${CONTENT_DIR}/blog/${SOURCE_LANG}/index.json"
|
|||
|
|
local target_blog_dir="${CONTENT_DIR}/blog/${lang}"
|
|||
|
|
local target_blog="${target_blog_dir}/index.json"
|
|||
|
|
|
|||
|
|
if [[ -f "$source_blog" ]]; then
|
|||
|
|
if [[ "$DRY_RUN" == "true" ]]; then
|
|||
|
|
log_info "[DRY RUN] Would sync blog structure for $lang"
|
|||
|
|
else
|
|||
|
|
mkdir -p "$target_blog_dir"
|
|||
|
|
|
|||
|
|
if [[ ! -f "$target_blog" ]]; then
|
|||
|
|
# Create new index with proper language
|
|||
|
|
jq --arg lang "$lang" '.language = $lang | .blog = []' "$source_blog" > "$target_blog"
|
|||
|
|
log_success "Created blog index for $lang"
|
|||
|
|
else
|
|||
|
|
log_info "Blog index already exists for $lang"
|
|||
|
|
fi
|
|||
|
|
fi
|
|||
|
|
fi
|
|||
|
|
fi
|
|||
|
|
|
|||
|
|
# Sync recipe structure
|
|||
|
|
if [[ "$CONTENT_TYPE" == "all" ]] || [[ "$CONTENT_TYPE" == "recipes" ]]; then
|
|||
|
|
local source_recipes="${CONTENT_DIR}/recipes/${SOURCE_LANG}/index.json"
|
|||
|
|
local target_recipes_dir="${CONTENT_DIR}/recipes/${lang}"
|
|||
|
|
local target_recipes="${target_recipes_dir}/index.json"
|
|||
|
|
|
|||
|
|
if [[ -f "$source_recipes" ]]; then
|
|||
|
|
if [[ "$DRY_RUN" == "true" ]]; then
|
|||
|
|
log_info "[DRY RUN] Would sync recipe structure for $lang"
|
|||
|
|
else
|
|||
|
|
mkdir -p "$target_recipes_dir"
|
|||
|
|
|
|||
|
|
if [[ ! -f "$target_recipes" ]]; then
|
|||
|
|
# Create new index with proper language
|
|||
|
|
jq --arg lang "$lang" '.language = $lang | .recipes = []' "$source_recipes" > "$target_recipes"
|
|||
|
|
log_success "Created recipe index for $lang"
|
|||
|
|
else
|
|||
|
|
log_info "Recipe index already exists for $lang"
|
|||
|
|
fi
|
|||
|
|
fi
|
|||
|
|
fi
|
|||
|
|
fi
|
|||
|
|
done
|
|||
|
|
|
|||
|
|
log_success "Content structure synchronization completed"
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
# Generate missing translations as placeholders
|
|||
|
|
generate_missing_translations() {
|
|||
|
|
log_step "Generating placeholder content for missing translations..."
|
|||
|
|
|
|||
|
|
if [[ "$CREATE_BACKUP" == "true" ]]; then
|
|||
|
|
create_backup
|
|||
|
|
fi
|
|||
|
|
|
|||
|
|
# Generate missing blog posts
|
|||
|
|
if [[ "$CONTENT_TYPE" == "all" ]] || [[ "$CONTENT_TYPE" == "blog" ]]; then
|
|||
|
|
local source_blog="${CONTENT_DIR}/blog/${SOURCE_LANG}/index.json"
|
|||
|
|
|
|||
|
|
if [[ -f "$source_blog" ]]; then
|
|||
|
|
local blog=$(jq -c '.blog[]' "$source_blog")
|
|||
|
|
|
|||
|
|
while IFS= read -r post; do
|
|||
|
|
local post_id=$(echo "$post" | jq -r '.id')
|
|||
|
|
|
|||
|
|
for lang in $(get_target_languages); do
|
|||
|
|
if [[ "$lang" == "$SOURCE_LANG" ]]; then
|
|||
|
|
continue
|
|||
|
|
fi
|
|||
|
|
|
|||
|
|
local target_blog="${CONTENT_DIR}/blog/${lang}/index.json"
|
|||
|
|
|
|||
|
|
if [[ -f "$target_blog" ]]; then
|
|||
|
|
local exists=$(jq -r ".blog[] | select(.id == \"$post_id\") | .id" "$target_blog" 2>/dev/null || echo "")
|
|||
|
|
|
|||
|
|
if [[ -z "$exists" ]]; then
|
|||
|
|
log_sync "Generating placeholder blog post: $post_id ($lang)"
|
|||
|
|
|
|||
|
|
if [[ "$DRY_RUN" == "true" ]]; then
|
|||
|
|
log_info "[DRY RUN] Would create placeholder for blog post '$post_id' in $lang"
|
|||
|
|
else
|
|||
|
|
# Create placeholder post with translated metadata
|
|||
|
|
local placeholder=$(echo "$post" | jq --arg lang "$lang" '
|
|||
|
|
.language = $lang |
|
|||
|
|
.title = .title + " [NEEDS TRANSLATION]" |
|
|||
|
|
.excerpt = .excerpt + " [This content needs to be translated to " + $lang + "]" |
|
|||
|
|
.source_file = "content/blog/" + $lang + "/" + .id + ".md" |
|
|||
|
|
.html_file = "/blog/" + $lang + "/" + .id + ".html" |
|
|||
|
|
.published = false
|
|||
|
|
')
|
|||
|
|
|
|||
|
|
# Add to target index
|
|||
|
|
local temp_file="/tmp/blog_sync_${lang}.json"
|
|||
|
|
jq --argjson new_post "$placeholder" '.blog += [$new_post]' "$target_blog" > "$temp_file"
|
|||
|
|
mv "$temp_file" "$target_blog"
|
|||
|
|
|
|||
|
|
log_success "Created placeholder blog post: $post_id ($lang)"
|
|||
|
|
fi
|
|||
|
|
fi
|
|||
|
|
fi
|
|||
|
|
done
|
|||
|
|
done <<< "$blog"
|
|||
|
|
fi
|
|||
|
|
fi
|
|||
|
|
|
|||
|
|
# Generate missing recipes
|
|||
|
|
if [[ "$CONTENT_TYPE" == "all" ]] || [[ "$CONTENT_TYPE" == "recipes" ]]; then
|
|||
|
|
local source_recipes="${CONTENT_DIR}/recipes/${SOURCE_LANG}/index.json"
|
|||
|
|
|
|||
|
|
if [[ -f "$source_recipes" ]]; then
|
|||
|
|
local recipes=$(jq -c '.recipes[]' "$source_recipes")
|
|||
|
|
|
|||
|
|
while IFS= read -r recipe; do
|
|||
|
|
local recipe_id=$(echo "$recipe" | jq -r '.id')
|
|||
|
|
|
|||
|
|
for lang in $(get_target_languages); do
|
|||
|
|
if [[ "$lang" == "$SOURCE_LANG" ]]; then
|
|||
|
|
continue
|
|||
|
|
fi
|
|||
|
|
|
|||
|
|
local target_recipes="${CONTENT_DIR}/recipes/${lang}/index.json"
|
|||
|
|
|
|||
|
|
if [[ -f "$target_recipes" ]]; then
|
|||
|
|
local exists=$(jq -r ".recipes[] | select(.id == \"$recipe_id\") | .id" "$target_recipes" 2>/dev/null || echo "")
|
|||
|
|
|
|||
|
|
if [[ -z "$exists" ]]; then
|
|||
|
|
log_sync "Generating placeholder recipe: $recipe_id ($lang)"
|
|||
|
|
|
|||
|
|
if [[ "$DRY_RUN" == "true" ]]; then
|
|||
|
|
log_info "[DRY RUN] Would create placeholder for recipe '$recipe_id' in $lang"
|
|||
|
|
else
|
|||
|
|
# Create placeholder recipe with translated metadata
|
|||
|
|
local placeholder=$(echo "$recipe" | jq --arg lang "$lang" '
|
|||
|
|
.language = $lang |
|
|||
|
|
.title = .title + " [NEEDS TRANSLATION]" |
|
|||
|
|
.excerpt = .excerpt + " [This content needs to be translated to " + $lang + "]" |
|
|||
|
|
.source_file = "content/recipes/" + $lang + "/" + .id + ".md" |
|
|||
|
|
.html_file = "/recipes/" + $lang + "/" + .id + ".html"
|
|||
|
|
')
|
|||
|
|
|
|||
|
|
# Add to target index
|
|||
|
|
local temp_file="/tmp/recipe_sync_${lang}.json"
|
|||
|
|
jq --argjson new_recipe "$placeholder" '.recipes += [$new_recipe]' "$target_recipes" > "$temp_file"
|
|||
|
|
mv "$temp_file" "$target_recipes"
|
|||
|
|
|
|||
|
|
log_success "Created placeholder recipe: $recipe_id ($lang)"
|
|||
|
|
fi
|
|||
|
|
fi
|
|||
|
|
fi
|
|||
|
|
done
|
|||
|
|
done <<< "$recipes"
|
|||
|
|
fi
|
|||
|
|
fi
|
|||
|
|
|
|||
|
|
log_success "Placeholder generation completed"
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
# Synchronize metadata while preserving translations
|
|||
|
|
sync_metadata() {
|
|||
|
|
log_step "Synchronizing metadata from $SOURCE_LANG while preserving translations..."
|
|||
|
|
|
|||
|
|
if [[ "$CREATE_BACKUP" == "true" ]]; then
|
|||
|
|
create_backup
|
|||
|
|
fi
|
|||
|
|
|
|||
|
|
# Sync blog metadata
|
|||
|
|
if [[ "$CONTENT_TYPE" == "all" ]] || [[ "$CONTENT_TYPE" == "blog" ]]; then
|
|||
|
|
local source_blog="${CONTENT_DIR}/blog/${SOURCE_LANG}/index.json"
|
|||
|
|
|
|||
|
|
if [[ -f "$source_blog" ]]; then
|
|||
|
|
for lang in $(get_target_languages); do
|
|||
|
|
if [[ "$lang" == "$SOURCE_LANG" ]]; then
|
|||
|
|
continue
|
|||
|
|
fi
|
|||
|
|
|
|||
|
|
local target_blog="${CONTENT_DIR}/blog/${lang}/index.json"
|
|||
|
|
|
|||
|
|
if [[ -f "$target_blog" ]]; then
|
|||
|
|
log_sync "Synchronizing blog metadata for: $lang"
|
|||
|
|
|
|||
|
|
if [[ "$DRY_RUN" == "true" ]]; then
|
|||
|
|
log_info "[DRY RUN] Would sync blog metadata for $lang"
|
|||
|
|
else
|
|||
|
|
# Preserve existing translations while updating metadata
|
|||
|
|
local temp_file="/tmp/blog_meta_sync_${lang}.json"
|
|||
|
|
|
|||
|
|
jq --slurpfile source "$source_blog" --arg lang "$lang" '
|
|||
|
|
.language = $lang |
|
|||
|
|
.blog = [
|
|||
|
|
.blog[] as $target |
|
|||
|
|
($source[0].blog[] | select(.id == $target.id)) as $source_post |
|
|||
|
|
if $source_post then
|
|||
|
|
$target |
|
|||
|
|
.author = $source_post.author |
|
|||
|
|
.date = $source_post.date |
|
|||
|
|
.read_time = $source_post.read_time |
|
|||
|
|
.tags = $source_post.tags |
|
|||
|
|
.featured = $source_post.featured |
|
|||
|
|
.published = $source_post.published |
|
|||
|
|
.source_file = ("content/blog/" + $lang + "/" + .id + ".md") |
|
|||
|
|
.html_file = ("/blog/" + $lang + "/" + .id + ".html")
|
|||
|
|
else
|
|||
|
|
$target
|
|||
|
|
end
|
|||
|
|
]
|
|||
|
|
' "$target_blog" > "$temp_file"
|
|||
|
|
|
|||
|
|
mv "$temp_file" "$target_blog"
|
|||
|
|
log_success "Synchronized blog metadata for: $lang"
|
|||
|
|
fi
|
|||
|
|
fi
|
|||
|
|
done
|
|||
|
|
fi
|
|||
|
|
fi
|
|||
|
|
|
|||
|
|
# Sync recipe metadata
|
|||
|
|
if [[ "$CONTENT_TYPE" == "all" ]] || [[ "$CONTENT_TYPE" == "recipes" ]]; then
|
|||
|
|
local source_recipes="${CONTENT_DIR}/recipes/${SOURCE_LANG}/index.json"
|
|||
|
|
|
|||
|
|
if [[ -f "$source_recipes" ]]; then
|
|||
|
|
for lang in $(get_target_languages); do
|
|||
|
|
if [[ "$lang" == "$SOURCE_LANG" ]]; then
|
|||
|
|
continue
|
|||
|
|
fi
|
|||
|
|
|
|||
|
|
local target_recipes="${CONTENT_DIR}/recipes/${lang}/index.json"
|
|||
|
|
|
|||
|
|
if [[ -f "$target_recipes" ]]; then
|
|||
|
|
log_sync "Synchronizing recipe metadata for: $lang"
|
|||
|
|
|
|||
|
|
if [[ "$DRY_RUN" == "true" ]]; then
|
|||
|
|
log_info "[DRY RUN] Would sync recipe metadata for $lang"
|
|||
|
|
else
|
|||
|
|
# Preserve existing translations while updating metadata
|
|||
|
|
local temp_file="/tmp/recipe_meta_sync_${lang}.json"
|
|||
|
|
|
|||
|
|
jq --slurpfile source "$source_recipes" --arg lang "$lang" '
|
|||
|
|
.language = $lang |
|
|||
|
|
.recipes = [
|
|||
|
|
.recipes[] as $target |
|
|||
|
|
($source[0].recipes[] | select(.id == $target.id)) as $source_recipe |
|
|||
|
|
if $source_recipe then
|
|||
|
|
$target |
|
|||
|
|
.category = $source_recipe.category |
|
|||
|
|
.difficulty = $source_recipe.difficulty |
|
|||
|
|
.prep_time = $source_recipe.prep_time |
|
|||
|
|
.tags = $source_recipe.tags |
|
|||
|
|
.featured = $source_recipe.featured |
|
|||
|
|
.source_file = ("content/recipes/" + $lang + "/" + .id + ".md") |
|
|||
|
|
.html_file = ("/recipes/" + $lang + "/" + .id + ".html")
|
|||
|
|
else
|
|||
|
|
$target
|
|||
|
|
end
|
|||
|
|
]
|
|||
|
|
' "$target_recipes" > "$temp_file"
|
|||
|
|
|
|||
|
|
mv "$temp_file" "$target_recipes"
|
|||
|
|
log_success "Synchronized recipe metadata for: $lang"
|
|||
|
|
fi
|
|||
|
|
fi
|
|||
|
|
done
|
|||
|
|
fi
|
|||
|
|
fi
|
|||
|
|
|
|||
|
|
log_success "Metadata synchronization completed"
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
# Validate synchronization
|
|||
|
|
validate_sync() {
|
|||
|
|
log_step "Validating content synchronization..."
|
|||
|
|
|
|||
|
|
local validation_errors=0
|
|||
|
|
|
|||
|
|
# Validate that all languages have the same structure
|
|||
|
|
for lang in "${SUPPORTED_LANGUAGES[@]}"; do
|
|||
|
|
log_info "Validating structure for language: $lang"
|
|||
|
|
|
|||
|
|
# Check blog structure
|
|||
|
|
local blog_index="${CONTENT_DIR}/blog/${lang}/index.json"
|
|||
|
|
if [[ ! -f "$blog_index" ]]; then
|
|||
|
|
log_warning "Missing blog index for language: $lang"
|
|||
|
|
((validation_errors++))
|
|||
|
|
else
|
|||
|
|
# Validate JSON syntax
|
|||
|
|
if ! jq empty "$blog_index" 2>/dev/null; then
|
|||
|
|
log_error "Invalid JSON in blog index for language: $lang"
|
|||
|
|
((validation_errors++))
|
|||
|
|
fi
|
|||
|
|
|
|||
|
|
# Check language field
|
|||
|
|
local file_lang=$(jq -r '.language' "$blog_index" 2>/dev/null || echo "")
|
|||
|
|
if [[ "$file_lang" != "$lang" ]]; then
|
|||
|
|
log_error "Language mismatch in blog index for $lang: expected '$lang', found '$file_lang'"
|
|||
|
|
((validation_errors++))
|
|||
|
|
fi
|
|||
|
|
fi
|
|||
|
|
|
|||
|
|
# Check recipe structure
|
|||
|
|
local recipe_index="${CONTENT_DIR}/recipes/${lang}/index.json"
|
|||
|
|
if [[ ! -f "$recipe_index" ]]; then
|
|||
|
|
log_warning "Missing recipe index for language: $lang"
|
|||
|
|
((validation_errors++))
|
|||
|
|
else
|
|||
|
|
# Validate JSON syntax
|
|||
|
|
if ! jq empty "$recipe_index" 2>/dev/null; then
|
|||
|
|
log_error "Invalid JSON in recipe index for language: $lang"
|
|||
|
|
((validation_errors++))
|
|||
|
|
fi
|
|||
|
|
|
|||
|
|
# Check language field
|
|||
|
|
local file_lang=$(jq -r '.language' "$recipe_index" 2>/dev/null || echo "")
|
|||
|
|
if [[ "$file_lang" != "$lang" ]]; then
|
|||
|
|
log_error "Language mismatch in recipe index for $lang: expected '$lang', found '$file_lang'"
|
|||
|
|
((validation_errors++))
|
|||
|
|
fi
|
|||
|
|
fi
|
|||
|
|
done
|
|||
|
|
|
|||
|
|
if [[ $validation_errors -eq 0 ]]; then
|
|||
|
|
log_success "Content synchronization validation passed!"
|
|||
|
|
else
|
|||
|
|
log_error "Validation failed with $validation_errors errors"
|
|||
|
|
return 1
|
|||
|
|
fi
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
# Main function
|
|||
|
|
main() {
|
|||
|
|
local command="$1"
|
|||
|
|
shift
|
|||
|
|
|
|||
|
|
echo -e "${BLUE}🔄 Content Synchronization Tool${NC}"
|
|||
|
|
echo "=================================="
|
|||
|
|
echo ""
|
|||
|
|
|
|||
|
|
check_dependencies
|
|||
|
|
|
|||
|
|
case "$command" in
|
|||
|
|
"sync-structure")
|
|||
|
|
parse_args "$@"
|
|||
|
|
sync_content_structure
|
|||
|
|
;;
|
|||
|
|
"sync-metadata")
|
|||
|
|
parse_args "$@"
|
|||
|
|
sync_metadata
|
|||
|
|
;;
|
|||
|
|
"check-missing")
|
|||
|
|
parse_args "$@"
|
|||
|
|
check_missing_content
|
|||
|
|
;;
|
|||
|
|
"generate-missing")
|
|||
|
|
parse_args "$@"
|
|||
|
|
generate_missing_translations
|
|||
|
|
;;
|
|||
|
|
"backup")
|
|||
|
|
create_backup
|
|||
|
|
;;
|
|||
|
|
"restore")
|
|||
|
|
parse_args "$@"
|
|||
|
|
restore_backup "$2"
|
|||
|
|
;;
|
|||
|
|
"validate")
|
|||
|
|
validate_sync
|
|||
|
|
;;
|
|||
|
|
"help"|"")
|
|||
|
|
show_usage
|
|||
|
|
;;
|
|||
|
|
*)
|
|||
|
|
log_error "Unknown command: $command"
|
|||
|
|
show_usage
|
|||
|
|
exit 1
|
|||
|
|
;;
|
|||
|
|
esac
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
# Run main function
|
|||
|
|
main "$@"
|