mirror of
https://github.com/streamyfin/streamyfin.git
synced 2026-03-06 09:46:17 +00:00
chore: add translation cleanup tooling and remove unused keys
- Add check-unused-translations.js script for detecting unused i18n keys - Remove unused player keys: refresh_tracks, audio_tracks, playback_state, index - Remove unused aspect_ratio section (7 keys) - Update copilot-instructions.md with i18n guidelines (only edit en.json, Crowdin handles other languages)
This commit is contained in:
121
scripts/check-unused-translations.js
Normal file
121
scripts/check-unused-translations.js
Normal file
@@ -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",
|
||||
);
|
||||
}
|
||||
@@ -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",
|
||||
|
||||
Reference in New Issue
Block a user