diff --git a/scripts/check-unused-translations.js b/scripts/check-unused-translations.js new file mode 100644 index 00000000..a9fed9e7 --- /dev/null +++ b/scripts/check-unused-translations.js @@ -0,0 +1,121 @@ +#!/usr/bin/env node +/** + * Check for unused translation keys in en.json + * Usage: bun run scripts/check-unused-translations.js [--remove] + */ + +const fs = require("node:fs"); +const path = require("node:path"); +const { execSync } = require("node:child_process"); + +const TRANSLATION_FILE = path.join(__dirname, "../translations/en.json"); +const REMOVE_UNUSED = process.argv.includes("--remove"); + +// Read translation file +const translations = JSON.parse(fs.readFileSync(TRANSLATION_FILE, "utf8")); + +// Flatten nested keys +function flattenKeys(obj, prefix = "") { + let keys = []; + for (const [key, value] of Object.entries(obj)) { + const fullKey = prefix ? `${prefix}.${key}` : key; + if (typeof value === "object" && value !== null && !Array.isArray(value)) { + keys = keys.concat(flattenKeys(value, fullKey)); + } else { + keys.push(fullKey); + } + } + return keys; +} + +// Search for key usage in codebase +function isKeyUsed(key) { + try { + // Escape special regex characters in the key + const escapedKey = key.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + + // Search in TypeScript/TSX files + const result = execSync( + `git grep -l "${escapedKey}" -- "*.ts" "*.tsx" 2>nul || echo ""`, + { + encoding: "utf8", + cwd: path.join(__dirname, ".."), + maxBuffer: 10 * 1024 * 1024, + }, + ).trim(); + + return result.length > 0; + } catch (_error) { + // If grep fails, assume key is used to be safe + return true; + } +} + +// Remove nested key from object +function removeNestedKey(obj, keyPath) { + const keys = keyPath.split("."); + const lastKey = keys.pop(); + let current = obj; + + for (const key of keys) { + if (!current[key]) return false; + current = current[key]; + } + + if (current[lastKey] !== undefined) { + delete current[lastKey]; + + // Clean up empty parent objects + if (Object.keys(current).length === 0 && keys.length > 0) { + removeNestedKey(obj, keys.join(".")); + } + return true; + } + return false; +} + +console.log("šŸ” Checking for unused translation keys...\n"); + +const allKeys = flattenKeys(translations); +const unusedKeys = []; + +for (const key of allKeys) { + if (!isKeyUsed(key)) { + unusedKeys.push(key); + } +} + +if (unusedKeys.length === 0) { + console.log("āœ… All translation keys are being used!"); + process.exit(0); +} + +console.log(`Found ${unusedKeys.length} unused translation keys:\n`); +for (const key of unusedKeys) { + console.log(` āŒ ${key}`); +} + +if (REMOVE_UNUSED) { + console.log("\nšŸ—‘ļø Removing unused keys..."); + + let removed = 0; + for (const key of unusedKeys) { + if (removeNestedKey(translations, key)) { + removed++; + } + } + + // Write back to file + fs.writeFileSync( + TRANSLATION_FILE, + `${JSON.stringify(translations, null, 2)}\n`, + "utf8", + ); + + console.log(`āœ… Removed ${removed} unused translation keys from en.json`); +} else { + console.log("\nšŸ’” Run with --remove flag to remove these keys from en.json"); + console.log( + " Example: bun run scripts/check-unused-translations.js --remove", + ); +} diff --git a/translations/en.json b/translations/en.json index d6ae888a..736123ed 100644 --- a/translations/en.json +++ b/translations/en.json @@ -253,29 +253,7 @@ }, "subtitle_color": "Subtitle Color", "subtitle_background_color": "Background Color", - "subtitle_font": "Subtitle Font", - "ksplayer_title": "KSPlayer Settings", - "hardware_decode": "Hardware Decoding", - "hardware_decode_description": "Use hardware acceleration for video decoding. Disable if you experience playback issues." - }, - "vlc_subtitles": { - "title": "VLC Subtitle Settings", - "hint": "Customize subtitle appearance for VLC player. Changes take effect on next playback.", - "text_color": "Text Color", - "background_color": "Background Color", - "background_opacity": "Background Opacity", - "outline_color": "Outline Color", - "outline_opacity": "Outline Opacity", - "outline_thickness": "Outline Thickness", - "bold": "Bold Text", - "margin": "Bottom Margin" - }, - "video_player": { - "title": "Video Player", - "video_player": "Video Player", - "video_player_description": "Choose which video player to use on iOS.", - "ksplayer": "KSPlayer", - "vlc": "VLC" + "subtitle_font": "Subtitle Font" }, "other": { "other_title": "Other", @@ -294,11 +272,6 @@ "UNKNOWN": "Unknown" }, "safe_area_in_controls": "Safe Area in Controls", - "video_player": "Video Player", - "video_players": { - "VLC_3": "VLC 3", - "VLC_4": "VLC 4 (Experimental + PiP)" - }, "show_custom_menu_links": "Show Custom Menu Links", "show_large_home_carousel": "Show Large Home Carousel (beta)", "hide_libraries": "Hide Libraries", @@ -599,10 +572,6 @@ "could_not_create_stream_for_chromecast": "Could not create a stream for Chromecast", "message_from_server": "Message from Server: {{message}}", "next_episode": "Next Episode", - "refresh_tracks": "Refresh Tracks", - "audio_tracks": "Audio Tracks:", - "playback_state": "Playback State:", - "index": "Index:", "continue_watching": "Continue Watching", "go_back": "Go Back", "downloaded_file_title": "You have this file downloaded", @@ -610,13 +579,6 @@ "downloaded_file_yes": "Yes", "downloaded_file_no": "No", "downloaded_file_cancel": "Cancel", - "aspect_ratio_title": "Aspect Ratio", - "aspect_ratio_original": "Original", - "aspect_ratio_original_description": "Use video's original aspect ratio", - "aspect_ratio_16_9_description": "Widescreen (most common)", - "aspect_ratio_4_3_description": "Traditional TV format", - "aspect_ratio_1_1_description": "Square format", - "aspect_ratio_21_9_description": "Ultra-wide cinematic", "playback_options_title": "Playback Options", "mpv_subtitle_settings_title": "MPV Subtitle Settings", "mpv_subtitle_settings_description": "Advanced subtitle customization for MPV player",