diff --git a/.roomodes b/.roomodes index 8dc2492f64..10625f66f5 100644 --- a/.roomodes +++ b/.roomodes @@ -22,9 +22,10 @@ "slug": "translate", "name": "Translate", "roleDefinition": "You are Roo, a linguistic specialist focused on translating and managing localization files. Your responsibility is to help maintain and update translation files for the application, ensuring consistency and accuracy across all language resources.", - "customInstructions": "When internationalizing and translating content:\n\n# Translation Style and Tone\n- Maintain a direct and concise style that mirrors the tone of the original text\n- Carefully account for colloquialisms and idiomatic expressions in both source and target languages\n- Aim for culturally relevant and meaningful translations rather than literal translations\n- Adapt the formality level to match the original content (whether formal or informal)\n- Preserve the personality and voice of the original content\n- Use natural-sounding language that feels native to speakers of the target language\n- Don't translate the word \"token\" as it means something specific in English that all languages will understand\n\n# Technical Implementation\n- Use namespaces to organize translations logically\n- Handle pluralization using i18next's built-in capabilities\n- Implement proper interpolation for variables using {{variable}} syntax\n- Don't include defaultValue. The `en` translations are the fallback.\n\n# Quality Assurance\n- Maintain consistent terminology across all translations\n- Respect the JSON structure of translation files\n- Watch for placeholders and preserve them in translations\n- Be mindful of text length in UI elements when translating to languages that might require more characters\n- Use context-aware translations when the same string has different meanings\n\n# Supported Languages\n- Localize all strings into the following locale files: ar, ca, cs, de, en, es, fr, hi, hu, it, ja, ko, pl, pt, pt-BR, ru, tr, zh-CN, zh-TW", + "customInstructions": "When internationalizing and translating content:\n\n# Translation Style and Tone\n- Maintain a direct and concise style that mirrors the tone of the original text\n- Carefully account for colloquialisms and idiomatic expressions in both source and target languages\n- Aim for culturally relevant and meaningful translations rather than literal translations\n- Adapt the formality level to match the original content (whether formal or informal)\n- Preserve the personality and voice of the original content\n- Use natural-sounding language that feels native to speakers of the target language\n- Don't translate the word \"token\" as it means something specific in English that all languages will understand\n\n# Technical Implementation\n- Use namespaces to organize translations logically\n- Handle pluralization using i18next's built-in capabilities\n- Implement proper interpolation for variables using {{variable}} syntax\n- Don't include defaultValue. The `en` translations are the fallback.\n- Always use apply_diff instead of write_to_file when editing existing translation files as it's much faster and more reliable\n- When using apply_diff, make sure to carefully identify the exact JSON structure to edit to avoid syntax errors\n\n# Quality Assurance\n- Maintain consistent terminology across all translations\n- Respect the JSON structure of translation files\n- Watch for placeholders and preserve them in translations\n- Be mindful of text length in UI elements when translating to languages that might require more characters\n- Use context-aware translations when the same string has different meanings\n- Always validate your translation work by running the missing translations script:\n ```\n node scripts/find-missing-translations.js\n ```\n- Before completing any translation task, ensure there are no missing translations by running the script with the target locale(s):\n ```\n node scripts/find-missing-translations.js --locale=\n ```\n- Address any missing translations identified by the script to ensure complete coverage across all locales\n\n# Supported Languages\n- Localize all strings into the following locale files: ar, ca, cs, de, en, es, fr, hi, hu, it, ja, ko, pl, pt, pt-BR, ru, tr, zh-CN, zh-TW", "groups": [ "read", + "command", [ "edit", { diff --git a/knip.json b/knip.json index 55b60e8210..ceb48b829c 100644 --- a/knip.json +++ b/knip.json @@ -16,7 +16,8 @@ "e2e/**", "src/activate/**", "src/exports/**", - "src/extension.ts" + "src/extension.ts", + "scripts/**" ], "workspaces": { "webview-ui": { diff --git a/scripts/find-missing-translations.js b/scripts/find-missing-translations.js new file mode 100644 index 0000000000..120d703ed0 --- /dev/null +++ b/scripts/find-missing-translations.js @@ -0,0 +1,218 @@ +/** + * Script to find missing translations in locale files + * + * Usage: + * node scripts/find-missing-translations.js [options] + * + * Options: + * --locale= Only check a specific locale (e.g. --locale=fr) + * --file= Only check a specific file (e.g. --file=chat.json) + * --help Show this help message + */ + +const fs = require("fs") +const path = require("path") + +// Process command line arguments +const args = process.argv.slice(2).reduce((acc, arg) => { + if (arg === "--help") { + acc.help = true + } else if (arg.startsWith("--locale=")) { + acc.locale = arg.split("=")[1] + } else if (arg.startsWith("--file=")) { + acc.file = arg.split("=")[1] + } + return acc +}, {}) + +// Show help if requested +if (args.help) { + console.log(` +Find Missing Translations + +A utility script to identify missing translations across locale files. +Compares non-English locale files to the English ones to find any missing keys. + +Usage: + node scripts/find-missing-translations.js [options] + +Options: + --locale= Only check a specific locale (e.g. --locale=fr) + --file= Only check a specific file (e.g. --file=chat.json) + --help Show this help message + +Output: + - Generates a report of missing translations + `) + process.exit(0) +} + +// Path to the locales directory +const LOCALES_DIR = path.join(__dirname, "../webview-ui/src/i18n/locales") + +// Recursively find all keys in an object +function findKeys(obj, parentKey = "") { + let keys = [] + + for (const [key, value] of Object.entries(obj)) { + const currentKey = parentKey ? `${parentKey}.${key}` : key + + if (typeof value === "object" && value !== null) { + // If value is an object, recurse + keys = [...keys, ...findKeys(value, currentKey)] + } else { + // If value is a primitive, add the key + keys.push(currentKey) + } + } + + return keys +} + +// Get value at a dotted path in an object +function getValueAtPath(obj, path) { + const parts = path.split(".") + let current = obj + + for (const part of parts) { + if (current === undefined || current === null) { + return undefined + } + current = current[part] + } + + return current +} + +// Main function to find missing translations +function findMissingTranslations() { + try { + // Get all locale directories (or filter to the specified locale) + const allLocales = fs.readdirSync(LOCALES_DIR).filter((item) => { + const stats = fs.statSync(path.join(LOCALES_DIR, item)) + return stats.isDirectory() && item !== "en" // Exclude English as it's our source + }) + + // Filter to the specified locale if provided + const locales = args.locale ? allLocales.filter((locale) => locale === args.locale) : allLocales + + if (args.locale && locales.length === 0) { + console.error(`Error: Locale '${args.locale}' not found in ${LOCALES_DIR}`) + process.exit(1) + } + + console.log(`Checking ${locales.length} non-English locale(s): ${locales.join(", ")}`) + + // Get all English JSON files + const englishDir = path.join(LOCALES_DIR, "en") + let englishFiles = fs.readdirSync(englishDir).filter((file) => file.endsWith(".json") && !file.startsWith(".")) + + // Filter to the specified file if provided + if (args.file) { + if (!englishFiles.includes(args.file)) { + console.error(`Error: File '${args.file}' not found in ${englishDir}`) + process.exit(1) + } + englishFiles = englishFiles.filter((file) => file === args.file) + } + + // Load file contents + const englishFileContents = englishFiles.map((file) => ({ + name: file, + content: JSON.parse(fs.readFileSync(path.join(englishDir, file), "utf8")), + })) + + console.log( + `Checking ${englishFileContents.length} translation file(s): ${englishFileContents.map((f) => f.name).join(", ")}`, + ) + + // Results object to store missing translations + const missingTranslations = {} + + // For each locale, check for missing translations + for (const locale of locales) { + missingTranslations[locale] = {} + + for (const { name, content: englishContent } of englishFileContents) { + const localeFilePath = path.join(LOCALES_DIR, locale, name) + + // Check if the file exists in the locale + if (!fs.existsSync(localeFilePath)) { + missingTranslations[locale][name] = { file: "File is missing entirely" } + continue + } + + // Load the locale file + const localeContent = JSON.parse(fs.readFileSync(localeFilePath, "utf8")) + + // Find all keys in the English file + const englishKeys = findKeys(englishContent) + + // Check for missing keys in the locale file + const missingKeys = [] + + for (const key of englishKeys) { + const englishValue = getValueAtPath(englishContent, key) + const localeValue = getValueAtPath(localeContent, key) + + if (localeValue === undefined) { + missingKeys.push({ + key, + englishValue, + }) + } + } + + if (missingKeys.length > 0) { + missingTranslations[locale][name] = missingKeys + } + } + } + + // Output results + let hasMissingTranslations = false + + console.log("\nMissing Translations Report:\n") + + for (const [locale, files] of Object.entries(missingTranslations)) { + if (Object.keys(files).length === 0) { + console.log(`āœ… ${locale}: No missing translations`) + continue + } + + hasMissingTranslations = true + console.log(`šŸ“ ${locale}:`) + + for (const [fileName, missingItems] of Object.entries(files)) { + if (missingItems.file) { + console.log(` - ${fileName}: ${missingItems.file}`) + continue + } + + console.log(` - ${fileName}: ${missingItems.length} missing translations`) + + for (const { key, englishValue } of missingItems) { + console.log(` ${key}: "${englishValue}"`) + } + } + + console.log("") + } + + if (!hasMissingTranslations) { + console.log("\nāœ… All translations are complete!") + } else { + console.log("āœļø To add missing translations:") + console.log("1. Add the missing keys to the corresponding locale files") + console.log("2. Translate the English values to the appropriate language") + console.log("3. Run this script again to verify all translations are complete") + } + } catch (error) { + console.error("Error:", error.message) + console.error(error.stack) + process.exit(1) + } +} + +// Run the main function +findMissingTranslations()